SwiftUI视图-viewDidLoad()?

Kra*_*und 10 ios swift swiftui

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

我遇到的主要问题是无论我尝试以哪种方式解决问题(绑定对象或使用State变量),我的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"
    }
}
Run Code Online (Sandbox Code Playgroud)
// 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"))
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
// 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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

提前致谢。

Mur*_*tam 31

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

  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?()
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)
  1. 创建View扩展:
extension View {

    func onLoad(perform action: (() -> Void)? = nil) -> some View {
        modifier(ViewDidLoadModifier(perform: action))
    }

}
Run Code Online (Sandbox Code Playgroud)
  1. 像这样使用:
struct SomeView: View {
    var body: some View {
        VStack {
            Text("HELLO!")
        }.onLoad {
            print("onLoad")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我同意这是这里列出的最佳解决方案,但它仍然存在视图必须首先出现才能执行 onLoad 函数的问题。在 iPad 上,您无法使用它来选择左侧导航窗格列表中的第一项,因为它最初不可见。 (4认同)
  • 很好的答案!尽管我会将其命名为“ViewFirstAppearModifier”,以更好地传达意图和行为。 (3认同)
  • 这个答案需要更多的赞成,因为它是唯一真正解决问题的答案 (2认同)

and*_*ive 19

我希望这是有帮助的。我找到了一篇博客文章,内容涉及在Appear上进行导航视图操作。

想法是您将服务烘焙到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)
    }
}
Run Code Online (Sandbox Code Playgroud)
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 = []
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

致谢:Majid Jabrayilov

  • 如果我错了,请纠正我,但在“onAppear”中使用“fetch”会导致每次出现视图时出现网络请求。(例如在 TabView 中)。 (12认同)
  • 完美,谢谢。那个`.onAppear()`是缺少的链接!! (6认同)
  • `onAppear` 更像是 `viewWillAppear` 或 `viewDidAppear`。问题是关于`viewDidLoad`。 (5认同)

Zgp*_*ace 17

Fully updated for Xcode 11.2, Swift 5.0

I think the viewDidLoad() just equal to implement in the body closure.
SwiftUI gives us equivalents to UIKit’s viewDidAppear() and viewDidDisappear() in the form of onAppear() and onDisappear(). You can attach any code to these two events that you want, and SwiftUI will execute them when they occur.

As an example, this creates two views that use onAppear() and onDisappear() to print messages, with a navigation link to move between the two:

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

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

  • 问题是关于“viewDidLoad”而不是“viewDidAppear”或“viewWillAppear”。 (18认同)

Sae*_*eed 13

我正在使用init()。我认为onApear()不是替代方案viewDidLoad()。因为 onApear 是在你的视图出现时被调用的。由于您的视图可以出现多次,因此它与viewDidLoad调用一次的视图发生冲突。

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

  • 但要记住一件事:当加载父级本身时,它的父级会调用 init() 。此外,父级将初始化所有潜在的子级,即使它们从未被加载。所以它也不是真正的“viewDidLoad()”,认为它只被调用一次。 (2认同)