SwiftUI LazyVGrid NavigationLink имеет необычную анимацию при возврате

#ios #swift #swiftui #swiftui-navigationlink #lazyvgrid

#iOS #swift #swiftui #swiftui-navigationlink #lazyvgrid

Вопрос:

У меня есть NavigationLink в LazyVGrid, и я получаю эту анимацию при возврате из представления сведений. Начиная примерно с 3,5 секунды этого видео, появляется анимация, которую я не ожидал. Между ячейками есть пробелы, и мне не нравится, как это выглядит.

Вот код для экрана с LazyVGrid:

 
import Foundation
import SwiftUI
import SFSafeSymbols
import CustomModalView

struct AlbumItemsScreen: View {
    @ObservedObject var viewModel:AlbumItemsViewModel
    let gridItemLayout = [GridItem(.adaptive(minimum: 50), spacing: 20)]
    @State var object: ServerObjectModel?
    @State var enableNavLink: Bool = false
    
    var body: some View {        
        RefreshableScrollView(refreshing: $viewModel.loading) {
            LazyVGrid(columns: gridItemLayout) {
                ForEach(viewModel.objects, id: .fileGroupUUID) { item in
                    AlbumItemsScreenCell(object: item)
                        .onTapGesture {
                            object = item
                            viewModel.showCellDetails = true
                        }

                    // Without this conditional, "spacer" cells show up in the grid.
                    if viewModel.showCellDetails, let object = object {
                        // The `NavigationLink` works here because the `MenuNavBar` contains a `NavigationView`.
                        NavigationLink(
                            destination:
                                // If I just use `item` directly in this-- oddly, it doesn't reference the same object as for `AlbumItemsScreenCell` above.
                                ObjectDetailsView(object: object),
                            isActive:
                                $viewModel.showCellDetails) {
                        }
                    } // end if
                } // end ForEach
            } // end LazyVGrid
            .padding(10)
        }
        .alert(isPresented: $viewModel.presentAlert, content: {
            let message:String = viewModel.alertMessage
            viewModel.alertMessage = nil
            return Alert(title: Text(message))
        })
        .navigationBarTitle("Album Contents")
        .navigationBarItems(trailing:
            AlbumItemsScreenNavButtons(viewModel: viewModel)
        )
        .disabled(viewModel.addNewItem)
        .modal(isPresented: $viewModel.addNewItem) {
            AddItemModal(viewModel: viewModel)
                .padding(20)
        }
        .modalStyle(DefaultModalStyle())
        .onDisappear {
            // I'm having a problem with the modal possibly being presented, the user navigating away, coming back and the modal still being present.
            // See also https://github.com/jankaltoun/CustomModalView/issues/1
            if viewModel.addNewItem == true {
                viewModel.addNewItem = false
            }
        }
    }
}

private struct AlbumItemsScreenNavButtons: View {
    @ObservedObject var viewModel:AlbumItemsViewModel
    
    var body: some View {
        HStack(spacing: 0) {
            Button(
                action: {
                    viewModel.sync()
                },
                label: {
                    SFSymbolNavBar(symbol: .goforward)
                }
            )
            
            Button(
                action: {
                    viewModel.startNewAddItem()
                },
                label: {
                    SFSymbolNavBar(symbol: .plusCircle)
                }
            )
        }
    }
}
 

(смотрите также https://github.com/SyncServerII/Neebla/blob/main/Neebla/UI/Screens/Album Items/AlbumItemsScreen.swift).

Вот код для просмотра сведений:

 
import Foundation
import SwiftUI
import SFSafeSymbols

struct ObjectDetailsView: View {
    let object:ServerObjectModel
    var model:MessagesViewModel?
    @State var showComments = false
    
    init(object:ServerObjectModel) {
        self.object = object
        model = MessagesViewModel(object: object)
    }
    
    var body: some View {
        VStack {
            AnyLargeMedia(object: object)
                .onTapGesture {
                    if let _ = model {
                        showComments = true
                    }
                }
                
            Spacer()
        }
        .navigationBarItems(trailing:
            Button(
                action: {
                    showComments = true
                },
                label: {
                    SFSymbolNavBar(symbol: .message)
                }
            )
            .enabled(model != nil)
        )
        .sheet(isPresented: $showComments) {
            if let model = model {
                CommentsView(model: model)
            }
            else {
                // Should never get here. Should never have showComments == true when model is nil.
                EmptyView()
            }
        }
    }
}
 

(смотрите также https://github.com/SyncServerII/Neebla/blob/main/Neebla/UI/Screens/Object Details/ObjectDetailsView.swift).

Я попробовал стратегию, указанную здесь https://developer.apple.com/forums/thread/125937 с этим:

                         NavigationLink(
                            destination:
                                // If I just use `item` directly in this-- oddly, it doesn't reference the same object as for `AlbumItemsScreenCell` above.
                                ObjectDetailsView(object: object),
                            isActive:
                                $viewModel.showCellDetails) {
                            EmptyView()
                        }
                        .frame(width: 0, height: 0)
                        .disabled(true) 
 

но происходит тот же эффект.

Ответ №1:

Что ж, это помогло мне сосредоточить мое внимание на проблеме, написав этот вопрос. Я придумал решение. Я убрал навигационную ссылку из scrollview и LazyVGrid:

 
import Foundation
import SwiftUI
import SFSafeSymbols
import CustomModalView

struct AlbumItemsScreen: View {
    @ObservedObject var viewModel:AlbumItemsViewModel
    let gridItemLayout = [GridItem(.adaptive(minimum: 50), spacing: 20)]
    @State var object: ServerObjectModel?
    
    var body: some View {
        VStack {
            RefreshableScrollView(refreshing: $viewModel.loading) {
                LazyVGrid(columns: gridItemLayout) {
                    ForEach(viewModel.objects, id: .fileGroupUUID) { item in
                        AlbumItemsScreenCell(object: item)
                            .onTapGesture {
                                object = item
                                viewModel.showCellDetails = true
                            }
                    } // end ForEach
                } // end LazyVGrid
                .padding(10)
            }
            
            if let object = object {
                // The `NavigationLink` works here because the `MenuNavBar` contains a `NavigationView`.
                NavigationLink(
                    destination:
                        ObjectDetailsView(object: object),
                    isActive:
                        $viewModel.showCellDetails) {
                    EmptyView()
                }
                .frame(width: 0, height: 0)
                .disabled(true)
            } // end if
        }
        .alert(isPresented: $viewModel.presentAlert, content: {
            let message:String = viewModel.alertMessage
            viewModel.alertMessage = nil
            return Alert(title: Text(message))
        })
        .navigationBarTitle("Album Contents")
        .navigationBarItems(trailing:
            AlbumItemsScreenNavButtons(viewModel: viewModel)
        )
        .disabled(viewModel.addNewItem)
        .modal(isPresented: $viewModel.addNewItem) {
            AddItemModal(viewModel: viewModel)
                .padding(20)
        }
        .modalStyle(DefaultModalStyle())
        .onDisappear {
            // I'm having a problem with the modal possibly being presented, the user navigating away, coming back and the modal still being present.
            // See also https://github.com/jankaltoun/CustomModalView/issues/1
            if viewModel.addNewItem == true {
                viewModel.addNewItem = false
            }
        }
    }
}

private struct AlbumItemsScreenNavButtons: View {
    @ObservedObject var viewModel:AlbumItemsViewModel
    
    var body: some View {
        HStack(spacing: 0) {
            Button(
                action: {
                    viewModel.sync()
                },
                label: {
                    SFSymbolNavBar(symbol: .goforward)
                }
            )
            
            Button(
                action: {
                    viewModel.startNewAddItem()
                },
                label: {
                    SFSymbolNavBar(symbol: .plusCircle)
                }
            )
        }
    }
}