Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI multiple NavigationLinks in Form/Sheet - entry stays highlighted

Tags:

ios

swift

swiftui

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")
    }
}

like image 263
leonboe1 Avatar asked Sep 17 '20 19:09

leonboe1


2 Answers

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:

like image 94
Kyle Avatar answered Nov 03 '22 21:11

Kyle


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.

like image 33
Mirko Avatar answered Nov 03 '22 22:11

Mirko