9

I have an issue with Xcode 12 / iOS 14. Using multiple NavigationLinks in a sheet with NavigationView leads to NavigationLink entries staying highlighted after going back a page. This is not only a problem with the simulator. See the attached GIF:

enter image description here

Does anybody know how to fix this?

Similar question: SwiftUI - NavigationLink cell in a Form stays highlighted after detail pop (but that's not the problem here).

struct ContentView: View {
    
    var body: some View {
        Text("")
            .sheet(isPresented: .constant(true), content: {
                NavigationView {
                    Form {
                        Section {
                            NavigationLink("Link to ViewB", destination: ViewB())
                        }
                    }
                    .navigationBarTitle("ViewA")
                }
            }) 
    }
}

struct ViewB: View {
    @State var selection = 0
    let screenOptions = ["a", "b", "c"]
    var body: some View{
        Form {
            Section {
                NavigationLink("Link to ViewC", destination: ViewC())
            }
        }
        .navigationBarTitle("ViewB")
    }
}

struct ViewC: View {
    var body: some View{
        Form {
            Section {
                Text("Test")
            }
        }
        .navigationBarTitle("ViewC")
    }
}

4
  • Looks like a bug in the sheet - works fine without it. You can file a bug report to Apple. Commented Sep 17, 2020 at 19:39
  • at last you did not encounter the bug of modulo (i % 2) == 0 not working :)))))))))))))))))) I laugh in rivers :) Commented Sep 17, 2020 at 19:55
  • 1
    Oh man, yes, I filed a bug report. Hopefully that gets fixed soon. Commented Sep 19, 2020 at 13:21
  • I ended up reimplementing Form altogether. Commented Dec 4, 2020 at 11:23

2 Answers 2

1

I've also run into this problem when using a NavigationLink inside a sheet. My solution on iOS 14 has been too Swizzle didSelectRowAt: of UITableView. When the row is selected, I deselect it. There is more code for detecting if its in a sheet, etc, but this is the basic, get it working code:

extension UITableView {
    
    @objc static func swizzleTableView() {
        
        guard self == UITableView.self else {
            return
        }
        
        let originalTableViewDelegateSelector = #selector(setter: self.delegate)
        let swizzledTableViewDelegateSelector = #selector(self.nsh_set(delegate:))
        
        let originalTableViewMethod = class_getInstanceMethod(self, originalTableViewDelegateSelector)
        let swizzledTableViewMethod = class_getInstanceMethod(self, swizzledTableViewDelegateSelector)
        
        method_exchangeImplementations(originalTableViewMethod!,
                                       swizzledTableViewMethod!)
    }
    
    @objc open func nsh_set(delegate: UITableViewDelegate?) {
        nsh_set(delegate: delegate)
        
        guard let delegate =  delegate else { return }
        
        let originalDidSelectSelector = #selector(delegate.tableView(_:didSelectRowAt:))
        let swizzleDidSelectSelector = #selector(self.tableView(_:didSelectRowAt:))
        
        let swizzleMethod = class_getInstanceMethod(UITableView.self, swizzleDidSelectSelector)
        let didAddMethod = class_addMethod(type(of: delegate), swizzleDidSelectSelector, method_getImplementation(swizzleMethod!), method_getTypeEncoding(swizzleMethod!))
        
        if didAddMethod {
            let didSelectOriginalMethod = class_getInstanceMethod(type(of: delegate), NSSelectorFromString("tableView:didSelectRowAt:"))
            let didSelectSwizzledMethod = class_getInstanceMethod(type(of: delegate), originalDidSelectSelector)
            if didSelectOriginalMethod != nil && didSelectSwizzledMethod != nil {
                method_exchangeImplementations(didSelectOriginalMethod!, didSelectSwizzledMethod!)
            }
        }
    }
    
    @objc open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        self.tableView(tableView, didSelectRowAt: indexPath)

        // This is specifically to fix a bug in SwiftUI, where a NavigationLink is
        // not de-selecting itself inside a sheet.
        tableView.deselectRow(at: indexPath,
                              animated: true)
        
    }
}

(Original swizzle code is from https://stackoverflow.com/a/59262109/127853), this code sample just adds the deselectRow call.)

Don't forget to call UITableView.swizzleTableView() somewhere such as application:didFinishLaunchingWithOptions:

Sign up to request clarification or add additional context in comments.

3 Comments

this crashes on iPad for me, on iPhone it works fine. Did you experience something similar?
@dehlen I have added additional code in the nsh_set method to detect if the delegate is a SwiftUI class and only replace the method if so because I was having issues on non-swiftUI code (and it's not needed on non SwiftUI code). I'm actively using this code in apps.apple.com/app/appwage/id834352667#?platform=iphone.
First of all thanks for getting back to me and for sharing your solution with us. On an iPhone this works for me. But running it on iPad results in an endless loop eventually crashing the application. Since I can't upload screenshots in a comment here is a link showing the debugger. As you can see it crashes on running the original implementation of didSelectRow. This is on any iPad running 14.2 with Xcode 12.2.0: cln.sh/ryVMcw
1

Add the following modifier to your NavigationView to set navigation view style and fix this issue:

.navigationViewStyle(StackNavigationViewStyle())

Explanation:

Default style is DefaultNavigationViewStyle(), from documentation: "The default navigation view style in the current context of the view being styled".

For some reason this will pick up DoubleColumnNavigationViewStyle instead of StackNavigationViewStyle on iPhone, if you set style explicitly it behaves as expected.

3 Comments

Not everbody wants to use iPhone layout on iPad.
Mentioned problem is, in my experience, specific to the iPhone, if you need to support StackNavigationViewStyle on iPadOS, you can set this style conditionally.
I see. Interesting.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.