Last active
March 9, 2025 16:52
-
-
Save curioustechizen/4d191dcd4d633aac62419f756ff0f650 to your computer and use it in GitHub Desktop.
SwiftUI, stateless views and embedding non-SwiftUI views using UIViewControllerRepresentable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct CorrectRepresentableExample: View { | |
@State private var clickCount: Int = 0 | |
var body: some View { | |
VStack { | |
TopView { | |
self.clickCount += 1 | |
} | |
.frame(maxHeight: .infinity) | |
CorrectStatefulBottomViewRepresentable(clickCount: self.$clickCount) | |
.frame(maxHeight: .infinity) | |
} | |
} | |
} | |
private struct CorrectStatefulBottomViewRepresentable: UIViewControllerRepresentable { | |
// To be able to propagate state changes as desired, we have to abandon the stateless approach | |
// Instead we need to use a stateful approach using SwiftUI @Binding | |
@Binding var clickCount: Int | |
func makeUIViewController(context: Context) -> UIViewController { | |
let bottomView = BottomView(clickCount: $clickCount) | |
return UIHostingController(rootView: bottomView) | |
} | |
func updateUIViewController(_ uiViewController: UIViewController, context: Context) { | |
// No need to update anything here, instead the @Binding var above results in state propagation | |
} | |
} | |
private struct TopView: View { | |
let onClick: () -> Void | |
var body: some View { | |
VStack { | |
Text("Correct UIVC Representable example") | |
Button("Click me", action: onClick) | |
} | |
} | |
} | |
private struct BottomView: View { | |
@Binding var clickCount: Int | |
var body: some View { | |
Text("\(clickCount) clicks") | |
} | |
} | |
#Preview { | |
CorrectRepresentableExample() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct IncorrectRepresentableExample: View { | |
@State private var clickCount: Int = 0 | |
var body: some View { | |
VStack { | |
TopView { | |
self.clickCount += 1 | |
} | |
.frame(maxHeight: .infinity) | |
IncorrectStatelessBottomViewRepresentable(clickCount: self.clickCount) | |
.frame(maxHeight: .infinity) | |
} | |
} | |
} | |
private struct IncorrectStatelessBottomViewRepresentable: UIViewControllerRepresentable { | |
// We try to use a stateless approach for a UIVCRepresentable. | |
// However this is incorrect: the state change is not propagated to the SwiftUI view | |
// embedded inside the UIHostingViewController | |
let clickCount: Int | |
func makeUIViewController(context: Context) -> UIViewController { | |
let bottomView = BottomView(clickCount: clickCount) | |
return UIHostingController(rootView: bottomView) | |
} | |
func updateUIViewController(_ uiViewController: UIViewController, context: Context) { | |
// If BottomView was a regular UIKit view, then we could have held on to an instance | |
// and updated a property here. | |
// However it is a SwiftUI view so that is not possible. | |
} | |
} | |
private struct TopView: View { | |
let onClick: () -> Void | |
var body: some View { | |
VStack { | |
Text("Incorrect UIVC Representable example") | |
Button("Click me", action: onClick) | |
} | |
} | |
} | |
private struct BottomView: View { | |
let clickCount: Int | |
var body: some View { | |
Text("\(clickCount) clicks") | |
} | |
} | |
#Preview { | |
IncorrectRepresentableExample() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct StatefulCounter: View { | |
@State private var clickCount = 0 | |
var body: some View { | |
VStack { | |
StatelessTopView { | |
self.clickCount += 1 | |
}.frame(maxHeight: .infinity) | |
StatelessBottomView(clickCount: self.clickCount) | |
.frame(maxHeight: .infinity) | |
} | |
} | |
} | |
private struct StatelessTopView: View { | |
let onClick: () -> Void | |
var body: some View { | |
Button("Click me", action: onClick) | |
} | |
} | |
private struct StatelessBottomView: View { | |
// This view is completely stateless. It only uses the clickCount property passed to it. | |
let clickCount: Int | |
var body: some View { | |
VStack { | |
Text("Stateless example") | |
Text("\(self.clickCount) clicks") | |
} | |
} | |
} | |
#Preview { | |
StatelessExample() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment