Swiftui View - Viewdidload()

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):

before

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:

Sample Image

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



Leave a reply



Submit