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.
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
@Stateproperty declares, that the underlying view should allocate storage for itself. The storage's lifetime is bound to the underlying view. A@Stateproperty (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@Stateproperty has no effect at all (which is good, since if it had, it would be a flaw).So, when you set the property
rootViewyou basically tell the hosting controller to use this SwiftUI View value to mutate the underlying view which renders the pixels.You should set the
rootViewevery 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:
Note that the SwiftUI view is used to tell how the underlying view should be created – respectively modified.
In your TestVC:
Call
update(_:)whenever the timer fires.