I have used SwiftUI to develop brief exercises and fix some bugs. NavigtaionView is one of the most annoying bugs, and this article will report the bugs to you.

# use UINavigationBarAppearance

In general case, we will be in the application (_, didFinishLaunchingWithOptions) using the following code to the UINavigationBar configuration:

UINavigationBar.appearance().isTranslucent = false
UINavigationBar.appearance().barTintColor = UIColor(named: "themeColor")
UINavigationBar.appearance().shadowImage = UIImage()
...

Copy the code

For SwiftUI in iOS 13.4 these codes will also work, but:

  • inIOS 13.3Will directly cause a crash, and can not be analyzed through the crash file;
  • If in the code once theNavigationViewSet up the.navigationViewStyle(StackNavigationViewStyle())So no matter what system version will be in thisNavigationViewIt crashes when it’s about to be shown;

It is recommended to use the new API of iOS 13, with an example of code with comments as follows:

let coloredAppearance = UINavigationBarAppearance(a)// Set it to opaque
coloredAppearance.configureWithOpaqueBackground()
// set the backgroundColor (for solid background you can use backgroundColor without setting backgroundImage)
coloredAppearance.backgroundColor = UIColor(named: "themeColor2")
// There is no need to set shadowColor, nil or clear for the same performance
coloredAppearance.shadowColor = .clear
let titleAttr: [NSAttributedString.Key: Any] = [
    .foregroundColor: UIColor.white
]
// Set the title attribute in the inline state. The large state corresponds to the largeTitleTextAttributes
coloredAppearance.titleTextAttributes = titleAttr
// You can also set titlePositionAdjustment to control the title offset UIOffset(Horizontal: -10, vertical: 0)
    
// Set the style of the return button
let backButtonAppearance = UIBarButtonItemAppearance(a)// Font style
backButtonAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.white]
coloredAppearance.backButtonAppearance = backButtonAppearance
    
let backImage = UIImage(named: "back")? .withRenderingMode(.alwaysOriginal)// Set the return button arrow image to nil
coloredAppearance.setBackIndicatorImage(backImage, transitionMaskImage: backImage)
// sets the appearance properties in the standard (large) state. CompactAppearance if not set will use the standardAppearance style
UINavigationBar.appearance().standardAppearance = coloredAppearance
// Describes the appearance properties of the navigation bar to use when the associated UIScrollView reaches the edge adjacent to the navigation bar (the top edge of the navigation bar).
// if not, a modified standardAppearance will be used.
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance

Copy the code

# NavigationLinkThe problem of

Create a new SwiftUI project and replace the contentView. swift code with the following:

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack(spacing: 30) {
                NavigationLink(destination: secondView()) {
                    Text("Use NavigationLink directly")
                }
            }
            .navigationBarTitle("Navigation BUG", displayMode: .inline)
            .navigationBarItems(
                trailing: NavigationLink(destination: secondView()) {
                    Text("Link")}}}func secondView(a) -> some View {
        return Text("second").navigationBarTitle("Sencod", displayMode: .inline)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()}}Copy the code

NavigationLink and NavigationLink should be NavigationLink and NavigationLink should be NavigationLink and NavigationLink should be NavigationLink. _? 13.1.? NavigationBar infinite overlay problem may occur on NavigationBar, this does not reproduce 😑);

NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink: NavigationLink

* * *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.Copy the code

The solution is to use NavigationLink’s other notation:

NavigationLink(destination: secondView(), isActive: $showDetail, label: { EmptyView()})Copy the code

Add this code to the ContentView struct as follows

struct ContentView: View {@State private var showDetail = false
    
    var body: some View {
        NavigationView {
            VStack(spacing: 30) {
                NavigationLink(destination: secondView()) {
                    Text("Use NavigationLink directly")}NavigationLink(destination: secondView(), isActive: $showDetail, label: { EmptyView()})Button(action: {
                    self.showDetail = true{})Text("Use isActive and EmptyView")
                }
            }
            .navigationBarTitle("Navigation BUG", displayMode: .inline)
            .navigationBarItems(
                leading: Button("Empty") {
                    self.showDetail = true
                },
                trailing: NavigationLink(destination: secondView()) {
                    Text("Link")
            })
        }
        .navigationViewStyle(StackNavigationViewStyle()}func secondView(a) -> some View {
        return Text("second").navigationBarTitle("Sencod", displayMode: .inline)
    }
}
Copy the code

After running, click Empty and use isActive and EmptyView to jump and return with no problems. OK, we’re done here? NO! We are too excited. If you run it on iOS 13.3, you will find that if you click Link to jump back and click Link again, you will not jump again. Similarly, click Empty and use NavigationLink directly. Same with isActive and EmptyView!!

The program didn’t crash but I was about to crash! There is no solution to this problem for the time being! If you have a good solution please be sure to leave a message notice, thank you.

It only took me less than 4 days to write and finish the application, but just adapting iOS 13.1, iOS 13.2.x, iOS 13.3 has already tested my hair amount;

So in order not to lengthen my hairline, I had to specify that iOS 13.4 was available in the second update to The Brief, but that’s not the end of it.

# In transition animation inNavigationViewThe problems in the

The overall code is as follows, and the problem code is commented:

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 uses ZStcak ==> Transition
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)
            }
    }
}
// NavigationView Group ==> Transition insert without removal,navigationBar perfect occlusion
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)
            }
        }
    }
}
// ZStack ==> Transition to insert without removal,navigationBar perfect occlusion
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)
            }
        }
    }
}
// Use ZStack ==> Transition without using 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)
            }
        }
    }
}

// Use ZStack internally
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}}}}// Use Group wrapping, layout is determined externally
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()}}Copy the code

Similarly, if you downloaded the official tutorial demo, find it

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)
    }
}
Copy the code

The removal effect is completely invalid in NavigationView 🤭.

# end?

These are just a few of the problems in NavigationView,

  • Other not very serious will not repeat;
  • andNavigationViewIrrelevant ones are also not described in this article (e.g., for useUIViewRepresentable çš„ ViewaddcornerRadiusModifiers cause it to be “unresponsive” 🤥);

All versions of iOS 13 are now supported. In order to fully adapt to iOS 13.x, I had to abandon NavigationView in SwiftUI and use the external UINavigationController to set UIHostingController to rootVC. UIHostingController uses the corresponding Swiftui-View initialization method to fix these problems.