SwiftUI onAppear 被调用两次
Posted
技术标签:
【中文标题】SwiftUI onAppear 被调用两次【英文标题】:SwifUI onAppear gets called twice 【发布时间】:2020-11-14 17:57:21 【问题描述】:Q1:为什么 onAppears 会被调用两次?
Q2:或者,我可以在哪里拨打网络电话?
我在代码中的几个不同位置放置了 onAppears,它们都被调用了两次。最终,我试图在显示下一个视图之前进行网络调用,所以如果你知道不使用 onAppear 的方法,我会全力以赴。
我还尝试在我的列表中放置和删除 ForEach,但它并没有改变任何东西。
Xcode 12 Beta 3 -> 目标 ios 14 CoreData 已启用但尚未使用
struct ChannelListView: View
@EnvironmentObject var channelStore: ChannelStore
@State private var searchText = ""
@ObservedObject private var networking = Networking()
var body: some View
NavigationView
VStack
SearchBar(text: $searchText)
.padding(.top, 20)
List()
ForEach(channelStore.allChannels) channel in
NavigationLink(destination: VideoListView(channel: channel)
.onAppear(perform:
print("PREVIOUS VIEW ON APPEAR")
))
ChannelRowView(channel: channel)
.listStyle(GroupedListStyle())
.navigationTitle("Channels")
struct VideoListView: View
@EnvironmentObject var videoStore: VideoStore
@EnvironmentObject var channelStore: ChannelStore
@ObservedObject private var networking = Networking()
var channel: Channel
var body: some View
List(videoStore.allVideos) video in
VideoRowView(video: video)
.onAppear(perform:
print("LIST ON APPEAR")
)
.navigationTitle("Videos")
.navigationBarItems(trailing: Button(action:
networking.getTopVideos(channelID: channel.channelId) (videos) in
var videoIdArray = [String]()
videoStore.allVideos = videos
for video in videoStore.allVideos
videoIdArray.append(video.videoID)
for (index, var video) in videoStore.allVideos.enumerated()
networking.getViewCount(videoID: videoIdArray[index]) (viewCount) in
video.viewCount = viewCount
videoStore.allVideos[index] = video
networking.setVideoThumbnail(video: video) (image) in
video.thumbnailImage = image
videoStore.allVideos[index] = video
)
Text("Button")
)
.onAppear(perform:
print("BOTTOM ON APPEAR")
)
【问题讨论】:
【参考方案1】:我也有同样的问题。
我做了以下事情:
struct ContentView: View
@State var didAppear = false
@State var appearCount = 0
var body: some View
Text("Appeared Count: \(appearrCount)"
.onAppear(perform: onLoad)
func onLoad()
if !didAppear
appearCount += 1
//This is where I loaded my coreData information into normal arrays
didAppear = true
这通过确保只有 onLoad() 的 if 条件内部的内容才会运行一次来解决这个问题。
更新:Apple 开发者论坛上的某个人提出了投诉,Apple 已意识到该问题。在 Apple 解决问题之前,我的解决方案是临时破解。
【讨论】:
是的,我刚刚验证了这在 15b1 中已修复。呃,我希望他们能向后移植这样的东西。 当视图被重新实例化时这不起作用,这就是我的情况【参考方案2】:你可以创建一个布尔变量来检查是否首先出现
struct VideoListView: View
@State var firstAppear: Bool = true
var body: some View
List
Text("")
.onAppear(perform:
if !self.firstAppear return
print("BOTTOM ON APPEAR")
self.firstAppear = false
)
【讨论】:
【参考方案3】:您可以为此错误创建第一个出现函数
extension View
/// Fix the SwiftUI bug for onAppear twice in subviews
/// - Parameters:
/// - perform: perform the action when appear
func onFirstAppear(perform: @escaping () -> Void) -> some View
let kAppearAction = "appear_action"
let queue = OperationQueue.main
let delayOperation = BlockOperation
Thread.sleep(forTimeInterval: 0.001)
let appearOperation = BlockOperation
perform()
appearOperation.name = kAppearAction
appearOperation.addDependency(delayOperation)
return onAppear
if !delayOperation.isFinished, !delayOperation.isExecuting
queue.addOperation(delayOperation)
if !appearOperation.isFinished, !appearOperation.isExecuting
queue.addOperation(appearOperation)
.onDisappear
queue.operations
.first $0.name == kAppearAction ?
.cancel()
【讨论】:
这使我的模拟器崩溃,queue.addOperation(delayOperation)
行出现此错误:*** -[NSOperationQueue addOperation:]: operation is already enqueued on a queue
@Jeehut 我更新了答案 :) 但我认为 Apple 正在解决这个问题,自上次 XCode 更新以来,我收到的重复 onAppear 调用减少了【参考方案4】:
我一直在使用类似的东西
import SwiftUI
struct OnFirstAppearModifier: ViewModifier
let perform:() -> Void
@State private var firstTime: Bool = true
func body(content: Content) -> some View
content
.onAppear
if firstTime
firstTime = false
self.perform()
extension View
func onFirstAppear( perform: @escaping () -> Void ) -> some View
return self.modifier(OnFirstAppearModifier(perform: perform))
我用它代替 .onAppear()
.onFirstAppear
self.vm.fetchData()
【讨论】:
【参考方案5】:对于仍然遇到此问题并使用 NavigationView 的所有人。将此行添加到根 NavigationView() 应该可以解决问题。
.navigationViewStyle(StackNavigationViewStyle())
从我尝试过的所有方法来看,这是唯一有效的方法。
【讨论】:
这对我不起作用。【参考方案6】:我们不必在.onAppear(perform)
上这样做
这可以在 View 的 init 上完成
【讨论】:
我将此解决方案与 ListView 和 DetailView 一起使用。在详细视图的初始化中,我正在调用我的演示者以从核心数据中获取数据。到目前为止,我只看到了 init 调用了一次,仍然看到了 onAppear 调用了 2 次。我喜欢这种方法,因为我不必在每个视图中添加任何 didAppear 布尔标志。 这个解决方案很糟糕,但似乎对我来说也最有效,因为我确实需要在每次我的视图再次出现时执行代码,而不仅仅是第一次。否则,每次加载数据时都会重复调用 onAppear。【参考方案7】:让我们假设您现在正在设计 SwiftUI,并且您的 PM 也是物理学家和哲学家。有一天他告诉你我们应该统一 UIView 和 UIViewController,就像量子力学和相对论一样。 OK,你和你的领导志同道合,投票支持“Simplicity is Tao”,并创建一个名为“View”的原子。现在你说:“视野就是一切,视野就是一切”。这听起来很棒,而且似乎可行。好吧,你提交代码并告诉 PM……。
onAppear
和onDisAppear
存在于每个视图中,但您真正需要的是页面生命周期回调。如果你像viewDidAppear
一样使用onAppear
,那么你会遇到两个问题:
-
受父视图影响,子视图会多次重建,导致
onAppear
被多次调用。
SwiftUI 是封闭源代码,但您应该知道:view = f(view)。所以,onAppear
将运行以返回一个新视图,这就是为什么 onAppear
会被调用两次。
我想告诉你onAppear
是对的!你必须改变你的想法。不要在onAppear
和onDisAppear
中运行生命周期代码!您应该在“行为区域”中运行该代码。例如,在导航到新页面的按钮中。
【讨论】:
你是对的绅士!谢谢我在init()而不是onAppear中制定了我的逻辑。谢谢!【参考方案8】:如果其他人在我的船上,我现在是这样解决的:
struct ChannelListView: View
@State private var searchText = ""
@State private var isNavLinkActive: Bool = false
@EnvironmentObject var channelStore: ChannelStore
@ObservedObject private var networking = Networking()
var body: some View
NavigationView
VStack
SearchBar(text: $searchText)
.padding(.top, 20)
List(channelStore.allChannels) channel in
ZStack
NavigationLink(destination: VideoListView(channel: channel))
ChannelRowView(channel: channel)
HStack
Spacer()
Button
isNavLinkActive = true
// Place action/network call here
label:
Image(systemName: "arrow.right")
.foregroundColor(.gray)
.listStyle(GroupedListStyle())
.navigationTitle("Channels")
【讨论】:
【参考方案9】:我有这个应用程序:
@main
struct StoriesApp: App
var body: some Scene
WindowGroup
TabView
NavigationView
StoriesView()
这是我的 StoriesView:
// ISSUE
struct StoriesView: View
@State var items: [Int] = []
var body: some View
List
ForEach(items, id: \.self) id in
StoryCellView(id: id)
.onAppear(perform: onAppear)
private func onAppear()
///////////////////////////////////
// Gets called 2 times on app start <--------
///////////////////////////////////
我通过测量 onAppear() 调用之间的 diff time 解决了这个问题。根据我的观察, onAppear() 的两次调用发生在 0.02 到 0.45 秒之间:
// SOLUTION
struct StoriesView: View
@State var items: [Int] = []
@State private var didAppearTimeInterval: TimeInterval = 0
var body: some View
List
ForEach(items, id: \.self) id in
StoryCellView(id: id)
.onAppear(perform: onAppear)
private func onAppear()
if Date().timeIntervalSince1970 - didAppearTimeInterval > 0.5
///////////////////////////////////////
// Gets called only once in 0.5 seconds <-----------
///////////////////////////////////////
didAppearTimeInterval = Date().timeIntervalSince1970
【讨论】:
【参考方案10】:就我而言,我发现层次结构中的一些视图,.onAppear()
(和.onDisappear()
)只被调用一次,正如预期的那样。我用它来发布我在需要对这些事件采取行动的视图中收听的通知。这是一个严重的黑客攻击,我已经验证该错误已在 iOS 15b1 中修复,但 Apple 确实需要向后移植该修复程序。
【讨论】:
以上是关于SwiftUI onAppear 被调用两次的主要内容,如果未能解决你的问题,请参考以下文章
iOS14.2 SwiftUI PageTabView 会多次调用 ChildView onAppear 方法
视图模型中的 Firestore 方法未在 SwiftUI onAppear 方法中调用
SwiftUI解决List子项无法正确触发onAppear和onDisappear事件的问题