【问题标题】:SwiftUI View - viewDidLoad()?SwiftUI 视图 - viewDidLoad()?
【发布时间】:2019-10-23 01:57:19
【问题描述】:

试图在视图加载后加载图像,驱动视图的模型对象(参见下面的 MovieDetail)有一个 urlString。因为 SwiftUI View 元素没有生命周期方法(并且没有视图控制器驱动事物),处理这个问题的最佳方法是什么?

我遇到的主要问题是无论我尝试哪种方式解决问题(绑定对象或使用状态变量),我的视图在加载之前都没有urlString...

// movie object
struct Movie: Decodable, Identifiable {

    let id: String
    let title: String
    let year: String
    let type: String
    var posterUrl: String

    private enum CodingKeys: String, CodingKey {
        case id = "imdbID"
        case title = "Title"
        case year = "Year"
        case type = "Type"
        case posterUrl = "Poster"
    }
}
// root content list view that navigates to the detail view
struct ContentView : View {

    var movies: [Movie]

    var body: some View {
        NavigationView {
            List(movies) { movie in
                NavigationButton(destination: MovieDetail(movie: movie)) {
                    MovieRow(movie: movie)
                }
            }
            .navigationBarTitle(Text("Star Wars Movies"))
        }
    }
}
// detail view that needs to make the asynchronous call
struct MovieDetail : View {

    let movie: Movie
    @State var imageObject = BoundImageObject()

    var body: some View {
        HStack(alignment: .top) {
            VStack {
                Image(uiImage: imageObject.image)
                    .scaledToFit()

                Text(movie.title)
                    .font(.subheadline)
            }
        }
    }
}

提前致谢。

【问题讨论】:

    标签: ios swift swiftui swiftui-view


    【解决方案1】:

    我希望这会有所帮助。我发现a blogpost 谈到了在onAppear 上为导航视图做一些事情。

    想法是您将服务烘焙到 BindableObject 中并在您的视图中订阅这些更新。

    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 = []
                    }
                }
            }
        }
    }
    

    感谢:Majid Jabrayilov

    【讨论】:

    • 如果我错了,请纠正我,但在 onAppear 中使用 fetch 会在每次出现视图时导致网络请求。 (例如在 TabView 中)。
    • 我真的希望有更好的方法。例如,我已经看到使用 onAppear 选择列表中的第一项的建议。这个策略是有缺陷的,因为在 iPad 上,左侧导航面板默认是隐藏的。无论视图是否可见,都需要有一种方法可以在加载视图时做一些工作。
    • onAppear 更像是viewWillAppearviewDidAppear。问题是关于viewDidLoad
    • @Armin 你找到解决办法了吗?
    • 想象一下拥有一个“TabView”。通过滑动页面 onApear() 被多次调用。然而 viewDidLoad() 只被调用一次。
    【解决方案2】:

    我们可以使用视图修饰符来实现这一点。

    1. 创建ViewModifier:
    struct ViewDidLoadModifier: ViewModifier {
    
        @State private var didLoad = false
        private let action: (() -> Void)?
    
        init(perform action: (() -> Void)? = nil) {
            self.action = action
        }
    
        func body(content: Content) -> some View {
            content.onAppear {
                if didLoad == false {
                    didLoad = true
                    action?()
                }
            }
        }
    
    }
    
    1. 创建View分机:
    extension View {
    
        func onLoad(perform action: (() -> Void)? = nil) -> some View {
            modifier(ViewDidLoadModifier(perform: action))
        }
    
    }
    
    1. 这样使用:
    struct SomeView: View {
        var body: some View {
            VStack {
                Text("HELLO!")
            }.onLoad {
                print("onLoad")
            }
        }
    }
    

    【讨论】:

    • 为什么会这样?我认为每次重新加载视图时都会覆盖 actionPerformed?
    • @Kyle 我已经更新了我的解决方案。现在它应该可以工作了,因为 State 在视图更改时保持它的值。
    • 这个答案需要更多的支持,因为它是唯一真正解决问题的答案
    • 这太棒了!!谢谢!!!这应该原生进入 SwiftUI。
    • 我同意这是这里列出的最佳解决方案,但它仍然存在视图必须首先出现才能执行 onLoad 功能的问题。在 iPad 上,您不能使用它来选择左侧导航窗格列表中的第一项,因为它最初是不可见的。
    【解决方案3】:

    针对 Xcode 11.2、Swift 5.0 进行了全面更新

    我认为viewDidLoad() 就等于在主体关闭中实现。
    SwiftUI 以 onAppear()onDisappear() 的形式为我们提供了 UIKit 的 viewDidAppear()viewDidDisappear() 等价物。您可以将任何代码附加到这两个事件上,SwiftUI 会在它们发生时执行它们。

    例如,这会创建两个视图,它们使用 onAppear()onDisappear() 来打印消息,并带有一个导航链接以在两者之间移动:

    struct ContentView: View {
        var body: some View {
            NavigationView {
                VStack {
                    NavigationLink(destination: DetailView()) {
                        Text("Hello World")
                    }
                }
            }.onAppear {
                print("ContentView appeared!")
            }.onDisappear {
                print("ContentView disappeared!")
            }
        }
    }
    

    参考:https://www.hackingwithswift.com/quick-start/swiftui/how-to-respond-to-view-lifecycle-events-onappear-and-ondisappear

    【讨论】:

    • 问题是关于viewDidLoad 而不是viewDidAppearviewWillAppear
    • @CharlieFish 他说onAppear相当于viewDidLoad。我不确定,但为什么苹果没有对此给出官方答复?
    • 这对我来说似乎是合理的,但我不确定在创建视图时设置的东西是否等于loadViewviewDidLoad。此外,SwiftUI View 和 UIKit View 有完全不同的生命周期(前者经常被重新创建),所以在 SwiftUI View 中可能没有直接等效的viewDidLoad
    【解决方案4】:

    我改用init()。我认为onApear() 不是viewDidLoad() 的替代品。因为 onApear 在您的视图出现时被调用。由于您的视图可能会出现多次,因此它与调用一次的 viewDidLoad 冲突。

    想象一下有一个TabView。通过滑动页面 onApear() 被多次调用。然而 viewDidLoad() 只被调用一次。

    【讨论】:

    • 谢谢,感谢您的回复。
    猜你喜欢
    • 2011-11-08
    • 2017-05-10
    • 2011-01-11
    • 1970-01-01
    • 1970-01-01
    • 2022-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多