How to Make the View Update Instant in Swiftui

How to make the View update instant in SwiftUI?

The issue is that exerciseTime[value] never changes, so the view is not redrawn.

Even though exerciseTime[value].timeIntervalSinceNow might be different, the actual exerciseTime[value] remains constant.

I recommend you use a Timer with an ObservableObject instead:

import Combine
import SwiftUI

class TimerViewModel: ObservableObject {
private var timer: AnyCancellable?

@Published var currentDate = Date()

func start(endDate: Date) {
timer = Timer.publish(every: 1.0, on: .main, in: .default)
.autoconnect()
.sink { [weak self] in
guard let self = self else { return }
self.currentDate = $0
if self.currentDate >= endDate {
self.timer = nil
}
}
}
}

and use it in your view:

struct TestView: View {
@State private var exerciseTime = Calendar.current.date(byAdding: .second, value: 15, to: Date())!
@StateObject private var timer = TimerViewModel()

var body: some View {
Group {
if timer.currentDate < exerciseTime {
Text(exerciseTime, style: .relative)
} else {
Text("0 sec")
}
}
.foregroundColor(.white)
.font(.largeTitle)
.padding(.top, 30)
.onAppear {
timer.start(endDate: exerciseTime)
}
}
}

How to dynamically update a SwiftUI View with an @Bindable value

The error appears because your function returns a Float and every function that gets called in a buildblock like HStack should return a view.

You should change the design of your Model to acomplish what you want.

Create a calculated var in your Model (this won´t affect Coredata).

class Reminder:Identifiable{
var name = ""
var totalDays = 0
var daysLeft = 0

var percentageLeft: Float {
Float(daysLeft) / Float(totalDays)
}

init(name:String, totalDays:Int, daysLeft:Int){
self.name = name
self.totalDays = totalDays
self.daysLeft = daysLeft
}
}

Then use this var to display the progress:

struct ContentView: View {
var reminders = [Reminder(name: "Cut the Grass", totalDays: 50, daysLeft: 10),
Reminder(name: "Power Wash Siding", totalDays: 30, daysLeft: 15)]

var body: some View {
List {
ForEach(reminders) { reminder in
HStack{
Text(reminder.name)
ProgressBar(progress: reminder.percentageLeft)
}
}
}
}
}

Aditional changes:

  • Depending on your overall design you may not need the Viewmodel here anymore.
  • I changed Float(daysLeft / totalDays) to Float(dayLeft) / Float(totalDays) as the first will allways produce 0
  • I changed @Bining var progress: Float to var progress: Float. You do not need a binding here. You only need one if the value of progress gets changed inside ProgressView and needs to get propagated to the parent ContentView

SwiftUI @Binding update doesn't refresh view

You have not misunderstood anything. A View using a @Binding will update when the underlying @State change, but the @State must be defined within the view hierarchy. (Else you could bind to a publisher)

Below, I have changed the name of your ContentView to OriginalContentView and then I have defined the @State in the new ContentView that contains your original content view.

import SwiftUI

struct OriginalContentView: View {
@Binding var isSelected: Bool

var body: some View {
Button(action: {
self.isSelected.toggle()
}) {
Text(isSelected ? "Selected" : "Not Selected")
}
}
}

struct ContentView: View {
@State private var selected = false

var body: some View {
OriginalContentView(isSelected: $selected)
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Updating UIViewController in right way in SwiftUI

With your current code you are creating new UIView on every update of UIViewController and putting it on top of already existing views, you are not just changing the colour of your custom UIView.


You could do something like this instead:

struct UIViewControllerRepresentableView: UIViewControllerRepresentable {

let viewColor: UIColor
let viewSize: CGSize

func makeUIViewController(context: UIViewControllerRepresentableContext<UIViewControllerRepresentableView>) -> UIViewControllerModel {
UIViewControllerModel(viewColor: viewColor, viewSize: viewSize)
}

func updateUIViewController(_ controller: UIViewControllerModel, context: UIViewControllerRepresentableContext<UIViewControllerRepresentableView>) {
controller.viewColor = viewColor
controller.viewSize = viewSize
}
}

class UIViewControllerModel: UIViewController {

var viewColor: UIColor {
didSet {
updateCustomViewColor()
}
}

var viewSize: CGSize {
didSet {
updateCustomViewSizeConstraints()
}
}

private var customView: UIView!
private var customViewHeightAnchor: NSLayoutConstraint!
private var customViewWidthAnchor: NSLayoutConstraint!

init(viewColor: UIColor, viewSize: CGSize) {
self.viewColor = viewColor
self.viewSize = viewSize
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
self.setupView()
}

private func setupView() {
customView = UIView()
customView.translatesAutoresizingMaskIntoConstraints = false
customView.backgroundColor = viewColor

view.addSubview(customView)

customViewHeightAnchor = customView.heightAnchor.constraint(equalToConstant: viewSize.height)
customViewWidthAnchor = customView.widthAnchor.constraint(equalToConstant: viewSize.width)

NSLayoutConstraint.activate([
customViewHeightAnchor,
customViewWidthAnchor,
customView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
customView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0)
])
}

private func updateCustomViewColor() {
customView.backgroundColor = viewColor
}

private func updateCustomViewSizeConstraints() {
customViewHeightAnchor.constant = viewSize.height
customViewWidthAnchor.constant = viewSize.width
}
}

By implementing your UIViewControllerRepresentableView this way, whenever SwiftUI updates your UIViewController viewColor property is set to a new value and didSet is getting called, which update customView backgroundColor.



Related Topics



Leave a reply



Submit