SwifUI onAppear вызывается дважды
Q1: Почему onAppears вызывается дважды?
Q2: Как я могу позвонить в сеть?
Я разместил onAppears в нескольких разных местах своего кода, и все они вызываются дважды. В конечном счете, я пытаюсь выполнить сетевой вызов перед отображением следующего представления, поэтому, если вы знаете, как сделать это без использования onAppear, я все слышу.
Я также попытался разместить и удалить ForEach в своих списках, и это ничего не меняет.
Xcode 12 Beta 3 -> Target 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")
})
}
}
10 ответов
Я использовал что-то вроде этого
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()
}
У меня была такая же проблема.
Я сделал следующее:
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 = false {
appearCount += 1
//This is where I loaded my coreData information into normal arrays
}
didAppear = true
}
}
Это решает проблему, гарантируя, что только то, что находится внутри условного if внутри onLoad(), будет выполняться один раз.
Обновление: кто-то на форумах разработчиков Apple подал заявку, и Apple знает об этой проблеме. Мое решение - временный взлом, пока Apple не решит проблему.
Предположим, вы сейчас разрабатываете SwiftUI, а ваш менеджер проектов также является физиком и философом. Однажды он скажет вам, что мы должны объединить UIView и UIViewController, как квантовую механику и теорию относительности. Хорошо, вы единомышленники со своим лидером, голосуете за «Простота — это Дао» и создаете атом под названием «Вид». Теперь вы говорите: «Вид — это все, вид — это все». Это звучит потрясающе и кажется осуществимым. Ну, вы фиксируете код и сообщаете PM….
и существует в каждом представлении, но вам действительно нужен обратный вызов жизненного цикла страницы. Если вы используете как
viewDidAppear
, то вы получаете две проблемы:
- Под влиянием родителя дочернее представление будет перестраиваться более одного раза, вызывая многократный вызов.
- SwiftUI имеет закрытый исходный код, но вы должны это знать: view = f(view). Итак, будет работать, чтобы вернуть новое представление, поэтому вызывается дважды.
Я хочу сказать вам, что это правильно! ВЫ ДОЛЖНЫ ИЗМЕНИТЬ СВОИ ИДЕИ. Не запускайте код жизненного цикла в
onAppear
а также
onDisAppear
! Вы должны запустить этот код в «Области поведения». Например, в кнопке перехода на новую страницу.
Вы можете создать переменную типа bool, чтобы проверить, появляется ли сначала
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
})
}
}
Для всех, у кого все еще есть эта проблема и которые используют NavigationView. Добавьте эту строку в корневой NavigationView(), и это должно решить проблему.
.navigationViewStyle(StackNavigationViewStyle())
Из всего, что я пробовал, это единственное, что сработало.
Вы можете создать функцию первого появления для этой ошибки
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()
}
}
}
Нам не нужно это делать
.onAppear(perform)
Это можно сделать при инициализации View
У меня есть это приложение:
@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(). По моим наблюдениям, двойные вызовы 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
}
}
В моем случае я обнаружил, что несколько просмотров вверх по иерархии,
Если в моей лодке находится кто-то еще, вот как я решил это сейчас:
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")
}
}
}
}