SwiftUI中NavigationView的问题
2020/5/3 23:25:32
本文主要是介绍SwiftUI中NavigationView的问题,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
本人已经使用 SwiftUI
开发了 简习录, 并修复了若干 BUG
. 在这些 BUG
中让人头痛的当属 NavigtaionView
了, 这篇文章将会向您报告其中的问题;
# 使用 UINavigationBarAppearance
在普通情况下我们会在 application(_, didFinishLaunchingWithOptions)
使用如下代码来对 UINavigationBar
进行配置:
UINavigationBar.appearance().isTranslucent = false UINavigationBar.appearance().barTintColor = UIColor(named: "themeColor") UINavigationBar.appearance().shadowImage = UIImage() ... 复制代码
对于 SwiftUI
在 iOS 13.4
这些代码也会起到作用, 但是:
- 在
iOS 13.3
将会直接引起崩溃,并且通过crash文件无法分析到原因; - 如果在代码中一旦对
NavigationView
设置.navigationViewStyle(StackNavigationViewStyle())
,那么不论什么系统版本都会在这个NavigationView
将要展示的时候直接崩溃;
这里推荐使用 iOS 13
的新 API
进行设置,带有注释的代码示例如下:
let coloredAppearance = UINavigationBarAppearance() // 设置为不透明 coloredAppearance.configureWithOpaqueBackground() // 设置背景色(对于纯色的背景可以使用 backgroundColor 而不需要设置 backgroundImage ) coloredAppearance.backgroundColor = UIColor(named: "themeColor2") // 完全可以不用设置 shadowColor, nil 或者 clear 为相同表现 coloredAppearance.shadowColor = .clear let titleAttr: [NSAttributedString.Key: Any] = [ .foregroundColor: UIColor.white ] // 设置 inline 状态下的 title 属性,large 状态对应的是 largeTitleTextAttributes coloredAppearance.titleTextAttributes = titleAttr // 这里还可以设置 titlePositionAdjustment 来控制标题的偏移量 UIOffset(horizontal: -10, vertical: 0) // 设置返回按钮的样式 let backButtonAppearance = UIBarButtonItemAppearance() // 字体样式 backButtonAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.white] coloredAppearance.backButtonAppearance = backButtonAppearance let backImage = UIImage(named: "back")?.withRenderingMode(.alwaysOriginal) // 设置返回按钮 箭头 image, 注意两个都要设置任何一个为 nil,则使用系统的 coloredAppearance.setBackIndicatorImage(backImage, transitionMaskImage: backImage) // 设置标准(large)状态下的外观属性,compactAppearance 如未设置将使用 standardAppearance 的样式 UINavigationBar.appearance().standardAppearance = coloredAppearance // 描述当相关联的UIScrollView到达与导航栏邻接的边缘(导航栏的顶部边缘)时要使用的导航栏的外观属性。 // 如果未设置,则将使用修改后的standardAppearance。 UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance 复制代码
# NavigationLink
的问题
新建一个 SwiftUI
工程,将 ContentView.swift
代码替换如下:
import SwiftUI struct ContentView: View { var body: some View { NavigationView { VStack(spacing: 30) { NavigationLink(destination: secondView()) { Text("直接使用NavigationLink") } } .navigationBarTitle("Navigation BUG", displayMode: .inline) .navigationBarItems( trailing: NavigationLink(destination: secondView()) { Text("Link") }) } } func secondView() -> some View { return Text("second").navigationBarTitle("Sencod", displayMode: .inline) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } 复制代码
当你在 iOS 13.4.x
和 iOS 13.1.x
的设备上运行后不论是点击 Link
还是 直接使用NavigationLink
, 然后点击返回都不会出现问题(?_? 13.1.? 上可能会出现 NavigationBar
无限叠加的问题,这个没有复现😑);
当你在 iOS 13.2
设备上运行时点击 直接使用NavigationLink
没任何问题, 但是点击 Link
然后返回,会在显示完转场动画后直接崩溃,控制台打印信息为:
*** Assertion failure in -[UINavigationController popToViewController:transition:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore_Sim/UIKit-3900.12.16/UINavigationController.m:8129 *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Tried to pop to a view controller that doesn't exist.' First throw call stack: ( 0 CoreFoundation 0x00007fff23c4f02e __exceptionPreprocess + 350 1 libobjc.A.dylib 0x00007fff50b97b20 objc_exception_throw + 48 2 CoreFoundation 0x00007fff23c4eda8 +[NSException raise:format:arguments:] + 88 3 Foundation 0x00007fff256c9b61 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191 4 UIKitCore 0x00007fff4713d9b1 __57-[UINavigationController popToViewController:transition:]_block_invoke + 620 5 UIKitCore 0x00007fff4713d65e -[UINavigationController popToViewController:transition:] + 753 ... 复制代码
解决办法是使用 NavigationLink
的另外一种写法:
NavigationLink(destination: secondView(), isActive: $showDetail, label: { EmptyView() }) 复制代码
添加此段代码后的 ContentView
struct 的整体代码如下
struct ContentView: View { @State private var showDetail = false var body: some View { NavigationView { VStack(spacing: 30) { NavigationLink(destination: secondView()) { Text("直接使用NavigationLink") } NavigationLink(destination: secondView(), isActive: $showDetail, label: { EmptyView() }) Button(action: { self.showDetail = true }) { Text("使用isActive和EmptyView") } } .navigationBarTitle("Navigation BUG", displayMode: .inline) .navigationBarItems( leading: Button("Empty") { self.showDetail = true }, trailing: NavigationLink(destination: secondView()) { Text("Link") }) } .navigationViewStyle(StackNavigationViewStyle()) } func secondView() -> some View { return Text("second").navigationBarTitle("Sencod", displayMode: .inline) } } 复制代码
运行后点击 Empty
和 使用isActive和EmptyView
跳转并返回无任何问题, OK 这里宣告结束? NO! 我们高兴的太早了, 在 iOS 13.3
的设备中运行你会发现如果点击 Link
跳转并返回后再次点击 Link
将不会再跳转, 同样的点击 Empty
, 直接使用NavigationLink
, 使用isActive和EmptyView
, 也是如此!!!
程序是没崩溃可是我已经要崩溃了! 这个问题暂时无解了! 如果您有好的解决办法请务必留言通知,感谢.
开始写到完成程序只花了 4 天不到的时间,可是单是适配个 iOS 13.1
, iOS 13.2.x
, iOS 13.3
就已经在考验我的发量了;
所以为了不拔高自己的发际线, 在 简习录 第二次的版本更新中, 我不得不指定 iOS 13.4
可用, 但是这并不是完结。
# 在 transition 动画在 NavigationView
中的问题
整体代码如下, 问题件代码注释:
import SwiftUI fileprivate extension AnyTransition { static var moveToOpacity: AnyTransition { let insertion = AnyTransition.move(edge: .bottom) let removal = AnyTransition.opacity return .asymmetric(insertion: insertion, removal: removal) } } fileprivate class CModel: Identifiable { let id = UUID() } struct BUGTransition: View { var body: some View { ShowOrHiddenAnimation2() } } // NavigationView 内部包裹的 ZStcak 使用 ZStcak ==> transition无问题,navigationBar 无法遮挡 fileprivate struct ShowOrHiddenAnimation4: View { @State private var model: CModel? = nil var body: some View { NavigationView { ZStack { Button("Tap Me") { withAnimation(.easeOut(duration: 3)) { self.model = CModel() } } if self.model != nil { FullScreenView1(model: $model) } } .navigationBarTitle("xxx", displayMode: .inline) } } } // ZStcak 内部包裹 NavigationView 后使用 Group ==> transition的insert无问题removal无效,navigationBar 完美遮挡 fileprivate struct ShowOrHiddenAnimation3: View { @State private var model: CModel? = nil var body: some View { ZStack { NavigationView { Button("Tap Me") { withAnimation(.easeOut(duration: 3)) { self.model = CModel() } } .navigationBarTitle("xxx", displayMode: .inline) } if self.model != nil { /* Color.pink.edgesIgnoringSafeArea(.all) .transition(.moveToOpacity) .animation(.easeOut(duration: 3)) .onTapGesture { withAnimation(.easeOut(duration: 3)) { self.model = nil } } */ FullScreenView2(model: $model) } } } } // ZStcak 内部包裹 NavigationView 后使用 ZStack ==> transition的insert无问题removal无效,navigationBar 完美遮挡 fileprivate struct ShowOrHiddenAnimation2: View { @State private var model: CModel? = nil var body: some View { ZStack { NavigationView { Button("Tap Me") { withAnimation(.easeOut(duration: 3)) { self.model = CModel() } } .navigationBarTitle("xxx", displayMode: .inline) } if self.model != nil { FullScreenView1(model: $model) } } } } // ZStack 内部直接使用 ZStack ==> transition无问题,未使用 navigationBar fileprivate struct ShowOrHiddenAnimation1: View { @State private var model: CModel? = nil var body: some View { ZStack { Button("Tap Me") { withAnimation(.easeOut(duration: 3)) { self.model = CModel() } } if self.model != nil { FullScreenView1(model: $model) } } } } // 内部指定使用 ZStack fileprivate struct FullScreenView1: View { @Binding var model: CModel? var body: some View { ZStack { Color.pink.edgesIgnoringSafeArea(.all) } .transition(.moveToOpacity) .animation(.easeOut(duration: 3)) .onTapGesture { withAnimation(.easeOut(duration: 3)) { self.model = nil } } } } // 使用 Group 包装,布局由外部决定 fileprivate struct FullScreenView2: View { @Binding var model: CModel? var body: some View { Group { Color.pink.edgesIgnoringSafeArea(.all) NavigationView { Text("FullScreenView2") } } .transition(.moveToOpacity) .animation(.easeOut(duration: 3)) .onTapGesture { withAnimation(.easeOut(duration: 3)) { self.model = nil } } } } struct BUGTransition_Previews: PreviewProvider { static var previews: some View { BUGTransition() } } 复制代码
同样的如果你下载了 官方教程demo ,找到
extension AnyTransition { static var moveAndFade: AnyTransition { let insertion = AnyTransition.move(edge: .trailing) .combined(with: .opacity) let removal = AnyTransition.scale .combined(with: .opacity) return .asymmetric(insertion: insertion, removal: removal) } } 复制代码
运行后也会发现 removal
效果在 NavigationView
中完全失效了🤭,这是官方彩蛋?
# 完结?
这里仅仅是列举了 NavigationView
中的一部分问题,
- 其他不是很严重的不再赘述;
- 和
NavigationView
不相关的也不在这篇中描述了(例如, 对使用UIViewRepresentable
的View
添加cornerRadius
修饰符会导致其“失去响应”🤥);
目前 简习录 已经支持 iOS 13
的全部系统版本.
最终为了完全适配 iOS 13.x
, 我不得不弃用 SwiftUI
中的 NavigationView
, 转而采用外部的 UINavigationController
设置 UIHostingController
为 rootVC
, UIHostingController
使用对应的 SwiftUI-View
来初始化的方式, 修复这些问题。
这篇关于SwiftUI中NavigationView的问题的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值