SwiftUI View - viewDidLoad()?
I hope this is helpful. I found a blogpost that talks about doing stuff onAppear for a navigation view.
Idea would be that you bake your service into a BindableObject and subscribe to those updates in your view.
struct SearchView : View {
@State private var query: String = "Swift"
@EnvironmentObject var repoStore: ReposStore
var body: some View {
NavigationView {
List {
TextField($query, placeholder: Text("type something..."), onCommit: fetch)
ForEach(repoStore.repos) { repo in
RepoRow(repo: repo)
}
}.navigationBarTitle(Text("Search"))
}.onAppear(perform: fetch)
}
private func fetch() {
repoStore.fetch(matching: query)
}
}
import SwiftUI
import Combine
class ReposStore: BindableObject {
var repos: [Repo] = [] {
didSet {
didChange.send(self)
}
}
var didChange = PassthroughSubject<ReposStore, Never>()
let service: GithubService
init(service: GithubService) {
self.service = service
}
func fetch(matching query: String) {
service.search(matching: query) { [weak self] result in
DispatchQueue.main.async {
switch result {
case .success(let repos): self?.repos = repos
case .failure: self?.repos = []
}
}
}
}
}
Credit to: Majid Jabrayilov
Accessing Value of SwiftUI View through a UIKit View
We can decorate the RatingView and use an ObservableObject to hold on to the source of truth.
class RatingObserver: ObservableObject {
@Published var rating: Int?
}
struct WrappedRatingView: View {
@ObservedObject var ratingObserver: RatingObserver
var body: some View {
RatingView(rating: $ratingObserver.rating)
}
}
Then we can use it in the following way.
class ViewController: UIViewController {
let ratingObserver = RatingObserver()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let hostingController = UIHostingController(
rootView: WrappedRatingView(ratingObserver: ratingObserver)
)
self.addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Reset Rating", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.addTarget(self, action: #selector(resetRating), for: .touchUpInside)
view.addSubview(button)
NSLayoutConstraint.activate([
hostingController.view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
hostingController.view.centerYAnchor.constraint(equalTo: view.centerYAnchor),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 100)
])
}
@objc func resetRating() {
ratingObserver.rating = nil
}
}
This allows for updating both the ViewController and the SwiftUI view.
UIViewControllerRepresentable is crashing on Xcode 14 iOS 16
Ouch... you use class for representable - that's bad idea (even for final!) - use only(!) structs
Here is fixed variant (tested with Xcode 14b2)
struct ViewController2_Previews: PreviewProvider {
static var previews: some View {
ViewControllerRep()
}
}
struct ViewControllerRep: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> ViewController2 {
return ViewController2()
}
func updateUIViewController(_ uiViewController: ViewController2, context: Context) {
}
}
Should I call viewDidLoad() inside updateUIViewController(_:context:) in SwiftUI
You are correct, we should never call our own viewDidLoad
.
Let’s diagnose the issue, using the view debugger. So, for example, here it is (setting max
to 8
to keep it manageable):
Note the height of the hosting controller’s view
is 800 (because we have 8 subviews, 100 pt each). So far, so good.
Now tap the “add” button and repeat:
We can see that the problem isn’t the scroll view, but rather the hosting view controller’s view. Even though there are now 10 items, it still thinks the hosting view controller’s view’s height is 800.
So, we can call setNeedsUpdateConstraints
and that fixes the problem:
func updateUIViewController(_ viewController: ScrollViewController<Content>, context: Context) {
viewController.hostingController.rootView = content()
viewController.hostingController.view.setNeedsUpdateConstraints()
}
Thus:
struct ContentView: View {
@State private var max = 8
var body: some View {
GeometryReader { geometry in // don't reference `UIScreen.main.bounds` as that doesn’t work in split screen multitasking
VStack {
Button("Add") { self.max += 2 }
ScrollableView {
ForEach(0..<self.max, id: \.self) { index in
Text("Hello \(index)")
.frame(width: geometry.size.width, height: 100)
.background(Color(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1)))
}
}
}
}
}
}
class ScrollViewController<Content: View>: UIViewController {
var hostingController: UIHostingController<Content>! = nil
init(rootView: Content) {
self.hostingController = UIHostingController<Content>(rootView: rootView)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad() // you need to call `super`
// self.view = UIView() // don't set `self.view`
addChild(hostingController)
view.addSubview(scrollView)
scrollView.addSubview(hostingController.view)
// scrollView.delegate = self // you're not currently using this delegate protocol, so we probably shouldn't set the delegate
// scrollView.scrollsToTop = true // these are the default values
// scrollView.isScrollEnabled = true
makeConstraints()
hostingController.didMove(toParent: self)
}
func makeConstraints() {
NSLayoutConstraint.activate([
// constraints for scroll view w/in main view
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// define contentSize of scroll view relative to hosting controller's view
hostingController.view.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor)
])
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
}
}
struct ScrollableView<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
func makeUIViewController(context: Context) -> ScrollViewController<Content> {
ScrollViewController(rootView: content())
}
func updateUIViewController(_ viewController: ScrollViewController<Content>, context: Context) {
viewController.hostingController.rootView = content()
viewController.hostingController.view.setNeedsUpdateConstraints()
}
}
Swift UI Convert UIView to View: Return type of property 'body' requires that 'UIView' conform to 'View'
You cannot use UIView
in a SwiftUI View
. In order to use you ViewController in SwiftUI
you need to wrap it in a UIViewControllerRepresentable
like so:
struct SomeView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> ViewController {
let viewController = ViewController()
//additional setup
return viewController
}
func updateUIViewController(_ uiViewController: ViewController, context: Context) {
//update Content
}
}
And your ContentView:
struct ContentView: View {
var body: some View {
SomeView()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Related Topics
How to Figure Out Which Url Is Being Blocked by App Transport Security
Push Notifications Not Being Received on iOS 10, But Working on iOS 9 and Before
How to Retrieve Facebook Response Using Facebook iOS Sdk
The Document Main.Storyboard Requires Xcode 8.0 or Later
Layout Attributes Relative to the Layout Margin on iOS Versions Prior to 8.0
Apn (Apple Push Notification) Payload Size Limit
An Error Occurred Uploading to the Itunes Store
How to Add a Button with Click Event on Uitableviewcell in Swift
App Store Connect Message: Your Account Will Soon Need to Be Migrated to Federated Auth
Why to Use Tuples When We Can Use Array to Return Multiple Values in Swift
Decrease the Width of the Last Line in Multiline Uilabel
How Does the Whatsapp Web Client Still Work with the Latest iOS Update (Sdk Version 13.0+)
How to Set Aspect Ratio Constraints Programmatically in iOS
Detect If the Application in Background or Foreground in Swift
How to Connect Viewcontroller.Swift to Viewcontroller in Storyboard
How to Have a Dynamic List of Views Using Swiftui
How to Handle Push Notifications If the Application Is Already Running