SwiftUI View @State变量在使用托管控制器时不更新

huangapple go评论53阅读模式
英文:

SwiftUI View @State variables not updating when using Hosting controller

问题

TestVC有一个定时器,每秒调用TestTimerFile中的一个函数。该函数应该更新SwiftUI视图中剩余的时间,但出现某种原因它没有更新。

注意:我在构建一个实际的应用程序时遇到了这个错误,我不能将实际的代码放在这里,因为这可能会令人困惑并且太长了。上面的代码只是一些示例代码,用来复制我遇到的这个错误。

英文:

I have a SwiftUI app that uses UIViewControllerRepresentable to show a UIViewController. And the view controller uses a hosting controller to show a SwiftUI view inside it.

RoomView (SwiftUI file)

struct RoomView: View {
    
    var body: some View {
        NavigationView {
            VStack {
                RoomUIView()
            }
            .ignoresSafeArea()
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}


// UIViewControllerRepresentable to show a UIView in SwiftUI
struct RoomUIView: UIViewControllerRepresentable {
    typealias UIViewControllerType = TestVC
    
    func makeUIViewController(context: Context) -> TestVC {
        let vc = TestVC()
        return vc
    }
    
    func updateUIViewController(_ uiViewController: TestVC, context: Context) {
        // Updates the state of the specified view controller with new information from SwiftUI.
    }
}

TestVC (UIKit file)

class TestVC: UIViewController {
    
    var testTimerFile = TestTimerFile()
    lazy var hostingController = UIHostingController(rootView: testTimerFile)
    var timer: Timer?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        hostingController = UIHostingController(rootView: testTimerFile)
        
        // Add the hosting controller's view as a child view
        addChild(hostingController)
        view.addSubview(hostingController.view)
        
        // Set up constraints
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
            hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        
        hostingController.didMove(toParent: self)
        
        
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.testTimerFile.startTimer()
        }
    }
}

TestTimerFile (SwiftUI file)

struct TestTimerFile: View {
    @State var timeRemaining = 30
    
    var body: some View {
        Text("\(timeRemaining)")
            .onAppear {
                startTimer()
            }
    }
    
    func startTimer() {
        print(timeRemaining)
        if timeRemaining > 0 {
            timeRemaining -= 1
            
        } else {
            timeRemaining = 30
        }
    }
    
}

TestVC has a timer that calls a function inside the TestTimerFile every second. That function is supposed to update the time remaining in the SwiftUI View but for some reason it doesn't update.

Note: I encountered this error when building an actual app, I cannot put the actual code here as it might be confusing and too long. The code above is just some sample code to replicate this error I was facing.

答案1

得分: 1

使用UIHostingController时,您可以通过设置属性rootView来更改底层的SwiftUI视图。

SwiftUI视图用于a)创建底层视图(渲染像素)和b)更改底层视图的状态,以便以不同方式进行渲染。

还要注意,SwiftUI视图值的生命周期与创建或更改所需的时间一样长,而底层视图将存在更长时间。

@State属性声明底层视图应为自己分配存储空间。存储的生命周期与底层视图绑定在一起。@State属性(很可能)仅在创建时对底层视图产生影响,除了视图在执行其主体时使用它来管理私有状态的事实之外。如果您在其他地方保留了一个没有关联底层视图的SwiftUI视图值,很可能@State属性根本不起作用(这是件好事,因为如果它起作用,那就是一个缺陷)。

因此,当您设置属性rootView时,基本上是告诉托管控制器使用此SwiftUI视图值来更改呈现像素的底层视图。

每当您希望更改视图时,都应设置rootView

您的TestVC可以充当"模型",负责计算新数据(即"视图状态"),这些数据应该呈现。

现在,应该变得简单:

创建一个SwiftUI视图:

struct TestTimerFile: View {
    let timeRemaining: Int
    
    var body: some View {
        Text("\(timeRemaining)")
    }
}

请注意,SwiftUI视图用于指示如何创建底层视图 - 或者修改底层视图。

在您的TestVC中:

func update(_ viewState: Int) {
   self.hostingController.rootView = .init(timeRemaining: viewState) 
}

每当计时器触发时调用update(_:)

英文:

When using a UIHostingController, you can change the underlying SwiftUI View by setting the property rootView.

A SwiftUI View is used to a) create an underlying view (rendering pixel) and b) mutate the state of that underlying view, so that it renders differently.

Note also, that the lifetime of a SwiftUI view value is just as long as it takes to make the creation or mutation, while the underlying view will exist for longer.

A @State property declares, that the underlying view should allocate storage for itself. The storage's lifetime is bound to the underlying view. A @State property (very likely) has only an effect on the underlying view, when it is created – beyond the fact, that the view uses it for managing private state while it executes it's body. If you keep a SwiftUI view value elsewhere which has no associated underlying view, it's very likely a @State property has no effect at all (which is good, since if it had, it would be a flaw).

So, when you set the property rootView you basically tell the hosting controller to use this SwiftUI View value to mutate the underlying view which renders the pixels.

You should set the rootView every time when you want your view to change.

Your TestVC may act as the "Model" which is responsible to calculate the new data (aka "view state") which should be rendered.

Now, it should become simple:

Make a SwiftUI View:

struct TestTimerFile: View {
    let timeRemaining: Int
    
    var body: some View {
        Text("\(timeRemaining)")
    }
}

Note that the SwiftUI view is used to tell how the underlying view should be created – respectively modified.

In your TestVC:

func update(_ viewState: Int) {
   self.hostingController.rootView = .init(timeRemaining: viewState) 
}

Call update(_:) whenever the timer fires.

huangapple
  • 本文由 发表于 2023年7月12日 21:44:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/76671267.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定