Tại sao ứng dụng SwiftUI của tôi gặp sự cố khi điều hướng lùi sau khi đặt `NavigationLink` bên trong` navigationBarItems` trong `NavigationView`?


47

Ví dụ tái tạo tối thiểu (Xcode 11.2 beta, điều này hoạt động trong Xcode 11.1):

struct Parent: View {
    var body: some View {
        NavigationView {
            Text("Hello World")
                .navigationBarItems(
                    trailing: NavigationLink(destination: Child(), label: { Text("Next") })
                )
        }
    }
}

struct Child: View {
    @Environment(\.presentationMode) var presentation
    var body: some View {
        Text("Hello, World!")
            .navigationBarItems(
                leading: Button(
                    action: {
                        self.presentation.wrappedValue.dismiss()
                    },
                    label: { Text("Back") }
                )
            )
    }
}

struct ContentView: View {
    var body: some View {
        Parent()
    }
}

Vấn đề dường như nằm ở việc đặt NavigationLinkbên trong của tôi một công cụ navigationBarItemssửa đổi được lồng bên trong chế độ xem SwiftUI có chế độ xem gốc là a NavigationView. Báo cáo sự cố chỉ ra rằng tôi đang cố gắng bật một bộ điều khiển xem không tồn tại khi tôi điều hướng tới Childvà sau đó quay lại Parent.

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Tried to pop to a view controller that doesn't exist.'
*** First throw call stack:

Nếu tôi thay vào đó đặt nó NavigationLinktrong phần thân của khung nhìn như bên dưới, thì nó hoạt động tốt.

struct Parent: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: Child(), label: { Text("Next") })
        }
    }
}

Đây có phải là một lỗi SwiftUI hoặc hành vi dự kiến?

EDIT: Tôi đã mở một vấn đề với Apple trong trợ lý phản hồi của họ với ID FB7423964trong trường hợp có ai đó từ Apple quan tâm đến việc cân nhắc :).

EDIT: Vé mở của tôi trong trợ lý phản hồi cho biết có hơn 10 vấn đề được báo cáo tương tự. Họ đã cập nhật độ phân giải với Resolution: Potential fix identified - For a future OS update. Ngón tay vượt qua mà sửa đất sớm.

EDIT: Điều này đã được sửa trong iOS 13.3!


Ví dụ bạn cung cấp ở trên chỉ hoạt động tốt với Xcode 11.2 beta. Chúng ta có thiếu thứ gì ở đây không?
Subramanian Mariappan

@SubramanianMariappan Nó cũng hoạt động tốt với tôi trên bản beta 11.2.
Farhan Amjad

1
Thật thú vị, nó sụp đổ cho tôi mỗi lần. Tôi thậm chí đã thử tạo một dự án mới và sao chép mã chính xác đó thay cho ContentView.swift. Tôi sẽ chỉnh sửa bài viết, nhưng sự cố chỉ xảy ra khi bạn điều hướng tiến lên rồi quay lại.
Robert

Câu hỏi tuyệt vời! Ví dụ của bạn ở đây sụp đổ cho tôi mỗi lần quá. Tôi chỉ đăng một câu trả lời mới hoạt động rất tốt cho tôi. Hãy cho tôi biết nếu nó làm việc cho bạn là tốt. Cảm ơn.
Chuck H

1
Cảm ơn các bản cập nhật liên quan đến vé táo!
malte

Câu trả lời:


20

Đây là một điểm khá đau đối với tôi! Tôi đã để nó cho đến khi hầu hết các ứng dụng của tôi được hoàn thành và tôi có không gian tâm trí để đối phó với sự cố.

Tôi nghĩ rằng tất cả chúng ta có thể đồng ý rằng có một số thứ khá tuyệt vời với SwifUI nhưng việc gỡ lỗi có thể khó khăn.

Theo ý kiến ​​của tôi, tôi sẽ nói rằng đây là một BUG. Đây là lý do của tôi:

  • Nếu bạn kết thúc cuộc gọi bỏ qua trình bàyMode trong độ trễ không đồng bộ khoảng nửa giây, bạn sẽ thấy rằng chương trình sẽ không còn bị sập nữa.

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        self.presentationMode.wrappedValue.dismiss()
    } 
  • Điều này gợi ý cho tôi rằng lỗi là một hành vi không mong muốn đi sâu vào cách giao diện SwiftUI với tất cả các mã UIKit khác để quản lý các chế độ xem khác nhau. Tùy thuộc vào mã thực tế của bạn, bạn có thể thấy rằng nếu có một số phức tạp nhỏ trong chế độ xem, sự cố thực sự sẽ không xảy ra. Ví dụ: nếu bạn đang chuyển từ chế độ xem sang chế độ xem có danh sách và danh sách đó trống, bạn sẽ gặp sự cố mà không có độ trễ không đồng bộ. Mặt khác, nếu bạn thậm chí chỉ có một mục trong chế độ xem danh sách đó, buộc lặp lại vòng lặp để tạo chế độ xem chính, bạn sẽ thấy rằng sự cố sẽ không xảy ra.

Tôi không chắc chắn giải pháp của tôi trong việc trì hoãn cuộc gọi bị trì hoãn là mạnh mẽ như thế nào. Tôi phải kiểm tra nó nhiều hơn nữa. Nếu bạn có ý tưởng về điều này, xin vui lòng cho tôi biết! Tôi rất vui khi được học từ bạn!


1
Rất thông minh! Tôi đã không nghĩ về điều đó. Hy vọng nó sẽ được sửa chữa sớm!
Robert

1
@Robert Nó đã khắc phục vấn đề của bạn? Đây là một vấn đề khó khăn vì một vấn đề không liên quan mà tôi đã tìm thấy là sử dụng Bộ chọn trong chế độ xem điều hướng trẻ em. Trong khi kiểu chọn của phân đoạn hoạt động, mặc định sẽ xuất hiện sự cố tại cùng một điểm, khi nhấp vào nút quay lại. Chúng ta có thể thảo luận thêm nếu nó vẫn khiến bạn đau buồn. Tái bút Tôi ghét giải pháp của tôi. Đó là một hack nhưng đó là một lỗi không cần cập nhật mã nếu Apple khắc phục vấn đề thời gian.
Justin Ngân

2
Tôi đồng ý rằng khía cạnh thời gian, cùng với thực tế là nó hoạt động tốt trong 11.1 và hoạt động bên ngoài các .navigationBarItems()điểm cho thấy đây là một lỗi.
John M.

3
Vâng, tôi tin rằng đó là một lỗi và đây là ứng cử viên hàng đầu hiện tại của tôi cho giải thưởng tiền thưởng. Vì tôi còn 4 ngày trên tiền thưởng kể từ thời điểm viết bài này, tôi chỉ giữ trong trường hợp có ai đi cùng với thông tin mới :).
Robert

1
Đây là một mẹo rất thú vị, cảm ơn vì điều đó! Thật không may, tôi vẫn đáng tin cậy làm hỏng ứng dụng trong trình giả lập 100% thời gian: / Nó hoạt động tốt hơn trên thiết bị, nhưng không phải là không bị lỗi gì cả. Nhưng đó cũng là trường hợp không chậm trễ.
Kilian

15

Điều này cũng đã làm tôi thất vọng trong một thời gian khá dài. Trong vài tháng qua, tùy thuộc vào phiên bản Xcode, phiên bản giả lập và loại và / hoặc phiên bản thiết bị thực, nó đã chuyển từ làm việc sang không hoạt động trở lại, dường như ngẫu nhiên. Tuy nhiên, gần đây nó đã thất bại liên tục đối với tôi, vì vậy ngày hôm qua tôi đã đi sâu vào nó. Tôi hiện đang sử dụng Xcode Phiên bản 11.2.1 (11B500).

Có vẻ như vấn đề xoay quanh Nav Bar và cách các nút được thêm vào nó. Vì vậy, thay vì sử dụng NavigationLink () cho chính nút, tôi đã thử sử dụng Nút tiêu chuẩn () với hành động đặt var @State kích hoạt NavigationLink ẩn. Đây là sự thay thế cho Chế độ xem Phụ huynh của Robert:

struct Parent: View {
    @State private var showingChildView = false
    var body: some View {
        NavigationView {
            VStack {
                Text("Hello World")
                NavigationLink(destination: Child(),
                               isActive: self.$showingChildView)
                { EmptyView() }
                    .frame(width: 0, height: 0)
                    .disabled(true)
                    .hidden()            
             }
             .navigationBarItems(
                 trailing: Button(action:{ self.showingChildView = true }) { Text("Next") }
             )
        }
    }
}

Đối với tôi, điều này hoạt động rất nhất quán trên tất cả các trình giả lập và tất cả các thiết bị thực.

Dưới đây là quan điểm của người trợ giúp của tôi:

struct HiddenNavigationLink<Destination : View>: View {

    public var destination:  Destination
    public var isActive: Binding<Bool>

    var body: some View {

        NavigationLink(destination: self.destination, isActive: self.isActive)
        { EmptyView() }
            .frame(width: 0, height: 0)
            .disabled(true)
            .hidden()
    }
}

struct ActivateButton<Label> : View where Label : View {

    public var activates: Binding<Bool>
    public var label: Label

    public init(activates: Binding<Bool>, @ViewBuilder label: () -> Label) {
        self.activates = activates
        self.label = label()
    }

    var body: some View {
        Button(action: { self.activates.wrappedValue = true }, label: { self.label } )
    }
}

Dưới đây là một ví dụ về việc sử dụng:

struct ContentView: View {
    @State private var showingAddView: Bool = false
    var body: some View {
        NavigationView {
            VStack {
                Text("Hello, World!")
                HiddenNavigationLink(destination: AddView(), isActive: self.$showingAddView)
            }
            .navigationBarItems(trailing:
                HStack {
                    ActivateButton(activates: self.$showingAddView) { Image(uiImage: UIImage(systemName: "plus")!) }
                    EditButton()
            } )
        }
    }
}

Tôi có thể xác nhận điều này hoạt động (thực sự tốt cho một vụ hack ;-))! Apple cần phải sửa lỗi này càng sớm càng tốt. Xcode 11.2.1, Catalina 10.15.2 (beta), iOS 13.2.2
P. Ent

1
Tôi đồng ý 100%. Nói chung, liên quan đến điều hướng trong SwiftUI, có rất nhiều thứ bị hỏng hoặc chỉ đơn giản là mất tích. Mà tất nhiên dẫn chúng ta đến vấn đề thực sự. Không có "nguồn sự thật" (tức là tài liệu và ví dụ) từ Apple, chỉ có những vụ hack như chúng tôi. BTW, tôi sử dụng kỹ thuật trên rất nhiều, tôi đã tạo ra hai khung nhìn tiện ích giúp ích rất nhiều cho khả năng đọc. Tôi sẽ thêm chúng vào câu trả lời của tôi trong trường hợp có ai quan tâm.
Chuck H

Cảm ơn vì cách giải quyết, nó chỉ hoạt động!
Stanislav Poslavsky

1
Điều này không làm việc cho tôi cho nhiều hơn một điều hướng. Khi bạn đã quay lại màn hình trước đó, liên kết vô hình không còn hoạt động.
Jon Shier

1
Tôi có một số thiết bị thực ở 13.3 (bản dựng 17C54) và tất cả chúng đều hoạt động như mong muốn. Vì tôi thực hiện hầu hết tất cả các thử nghiệm của mình trên các thiết bị thực, nên tôi không sử dụng trình giả lập rất thường xuyên. Nhưng tôi đã thử trường hợp thử nghiệm của mình trên một trình giả lập 13.3 và thử nghiệm không thành công ở đó. Tôi đã nhận thấy rằng iOS 13.3 trên trình giả lập Xcode là bản dựng trước đó (17C45) so với bản cập nhật công khai. Tôi sẽ quan tâm để biết nếu có ai quan sát hành vi thất bại trên một thiết bị thực sự.
Chuck H

12

Đây là một lỗi lớn và tôi không thể thấy một cách thích hợp để khắc phục nó. Hoạt động tốt trong iOS 13 / 13.1 nhưng 13.2 gặp sự cố.

Bạn thực sự có thể sao chép nó theo cách đơn giản hơn nhiều (mã này đúng là tất cả những gì bạn cần).

struct ContentView: View {
    var body: some View {
        NavigationView {
            Text("Hello, World!").navigationBarTitle("To Do App")
                .navigationBarItems(leading: NavigationLink(destination: Text("Hi")) {
                    Text("Nav")
                    }
            )
        }
    }
}

Hy vọng Apple sắp xếp nó vì nó chắc chắn sẽ phá vỡ vô số ứng dụng SwiftUI (bao gồm cả ứng dụng của tôi).


Haha ... Điều đó thật tuyệt vời. Bạn đã điều hướng đến chế độ xem Văn bản trong SwiftUI, là chế độ xem! Vâng, điều đó nên điều hướng trở lại đó là cha mẹ không? Tuy nhiên, nó không. Thật thú vị khi hành vi từ ví dụ của bạn phá vỡ giao diện người dùng nhưng thực sự không gây ra sự cố nghiêm trọng.
Justin Ngân

Vâng, khả năng kết hợp của SwiftUI (và React Native / Flutter, v.v.) thật đáng kinh ngạc. Cung cấp cho bạn rất nhiều kiểm soát / tính linh hoạt (khi nó hoạt động ít nhất).
James

1
Xác nhận sự cố này trên Catalina (10.15.1), Xcode (11.2.1), iOS (13.2.2)
P. Ent

Nó không còn gặp sự cố trong 13.3, tuy nhiên điều hướng dường như chỉ hoạt động trong lần đầu tiên bạn kích hoạt nó 🤦‍♂️
James

6

Như một giải pháp thay thế, dựa trên câu trả lời của Chuck H ở trên, tôi đã gói gọn NavigationLink như một yếu tố ẩn:

struct HiddenNavigationLink<Content: View>: View {
var destination: Content
@Binding var activateLink: Bool

var body: some View {
    NavigationLink(destination: destination, isActive: self.$activateLink) {
        EmptyView()
    }
    .frame(width: 0, height: 0)
    .disabled(true)
    .hidden()
}
}

Sau đó, bạn có thể sử dụng nó trong NavigationView (điều rất quan trọng) và kích hoạt nó từ Nút trong thanh điều hướng:

VStack {
    HiddenNavigationList(destination: SearchView(), activateLink: self.$searchActivated)
    ...
}
.navigationBarItems(trailing: 
    Button("Search") { self.searchActivated = true }
)

Đóng gói này trong các bình luận "// HACK" để khi Apple sửa lỗi này, bạn có thể thay thế nó.


Điều này dường như chỉ hoạt động trên lần sử dụng đầu tiên trong iOS 13.3.
James

3

Dựa trên thông tin mà các bạn đã cung cấp và đặc biệt là một nhận xét mà @Robert đã đưa ra về vị trí của NavigationView, tôi đã tìm ra cách khắc phục vấn đề ít nhất là theo kịch bản cụ thể của mình.

Trong trường hợp của tôi, tôi đã có một TabView được đặt trong NavigationView như thế này:

struct ContentViewThatCrashes: View {
@State private var selection = 0

var body: some View {
    NavigationView{
        TabView(selection: $selection){
            NavigationLink(destination: NewView()){
                Text("First View")
                    .font(.title)
            }
            .tabItem {
                VStack {
                    Image("first")
                    Text("First")
                }
            }
            .tag(0)
            NavigationLink(destination: NewView()){
                Text("Second View")
                    .font(.title)
            }
            .tabItem {
                VStack {
                    Image("second")
                    Text("Second")
                }
            }
            .tag(1)
        }
    }
  }
}

Mã này gặp sự cố khi mọi người đang báo cáo trong iOS 13.2 và hoạt động trong iOS 13.1. Sau một số nghiên cứu, tôi đã tìm ra một cách giải quyết cho tình huống này.

Về cơ bản, tôi đang di chuyển NavigationView đến từng màn hình riêng biệt trên mỗi tab như thế này:

struct ContentViewThatWorks: View {
@State private var selection = 0

var body: some View {
    TabView(selection: $selection){
        NavigationView{
            NavigationLink(destination: NewView()){
                Text("First View")
                    .font(.title)
            }
        }
        .tabItem {
            VStack {
                Image("first")
                Text("First")
            }
        }
        .tag(0)
        NavigationView{
            NavigationLink(destination: NewView()){
                Text("Second View")
                    .font(.title)
            }
        }
        .tabItem {
            VStack {
                Image("second")
                Text("Second")
            }
        }
        .tag(1)
    }
  }
}

Bằng cách nào đó đi ngược lại tiền đề đơn giản của SwiftUI nhưng nó hoạt động trên iOS 13.2.


Điều này hoạt động nhưng, vấn đề là loại bỏ các tabView trên NewView.
THỨ SÁU 13/11/19

1
@FRIDDAY ví dụ này hoạt động trong 13.1 nhưng gặp sự cố trong 13.2. Đó là một lỗi đã biết và ý định của tôi là cố gắng giúp ai đó trong cùng một kịch bản với cách giải quyết
Julio Bailon

1

Xcode 11.2.1 Swift 5

HIỂU RỒI! Tôi mất vài ngày để tìm ra điều này ...

Trong trường hợp của tôi khi sử dụng SwiftUI, tôi chỉ gặp sự cố nếu phần dưới cùng của danh sách mở rộng ra ngoài màn hình và sau đó tôi cố gắng "di chuyển" bất kỳ mục danh sách nào. Điều cuối cùng tôi phát hiện ra là nếu tôi có quá nhiều "thứ" bên dưới Danh sách () thì nó sẽ gặp sự cố khi di chuyển. Chẳng hạn, bên dưới Danh sách của tôi () Tôi đã có Nút văn bản (), Spacer (), Nút (), Spacer () (). Nếu tôi nhận xét bất kỳ MỘT trong số các đối tượng đó thì đột nhiên tôi không thể tạo lại vụ tai nạn. Tôi không chắc chắn những hạn chế là gì, nhưng nếu bạn gặp sự cố này thì hãy thử xóa các đối tượng bên dưới danh sách của bạn để xem nó có giúp ích không.


0

Mặc dù tôi không thể thấy bất kỳ sự cố nào, mã của bạn có một số vấn đề:

bằng cách đặt mục hàng đầu, bạn thực sự giết hành vi mặc định của các chuyển tiếp điều hướng. (thử vuốt từ phía đầu để xem nó có hoạt động không).

Vì vậy, không cần phải có một nút ở đó. Chỉ cần để nó như vậy và bạn có một nút quay lại miễn phí.

Và đừng quên theo HIG , tiêu đề nút quay lại sẽ hiển thị nơi nó đi chứ không phải nó là gì! Vì vậy, hãy thử đặt tiêu đề cho trang đầu tiên để hiển thị cho nó một nút quay lại bất kỳ.

struct Parent: View {
    var body: some View {
        NavigationView {
            Text("Hello World")
                .navigationBarItems(
                    trailing: NavigationLink(destination: Child(), label: { Text("Next") })
                )
                .navigationBarTitle("First Page",displayMode: .inline)
        }
    }
}

struct Child: View {
    @Environment(\.presentationMode) var presentation
    var body: some View {
        Text("Hello, World!")
    }
}

struct ContentView: View {
    var body: some View {
        Parent()
    }
}

1
Này, cảm ơn vì câu trả lời. Mặc dù tôi đồng ý rằng việc để lại hành vi nút quay lại mặc định là mong muốn, nhưng nó vẫn tạo ra sự cố.
Robert

Phiên bản nào bạn đang sử dụng? Tôi đã thử nó trước khi gửi. Có lẽ bạn có một vấn đề khác. Bạn có thể cung cấp một dự án mẫu xin vui lòng?
Mojtaba Hosseini

1
Xcode 11.2 beta giống như câu hỏi nói. Ví dụ tôi cung cấp trong câu hỏi là tất cả những gì bạn cần để tái tạo sự cố.
Robert

Tôi đang sử dụng cùng một phiên bản và cùng mã nhưng không gặp sự cố 🤔
Mojtaba Hosseini

1
Xác nhận sự cố này trên Catalina (10.15.1), Xcode (11.2.1), iOS (13.2.2)
P. Ent

0

FWIW - Các giải pháp trên cho thấy Hack NavigationLink ẩn vẫn là cách giải quyết tốt nhất trong iOS 13.3b3. Tôi cũng đã nộp một FB7386339 vì lợi ích của hậu thế và đã bị đóng tương tự như các FB đã nói ở trên: "Đã sửa lỗi tiềm năng - Để cập nhật hệ điều hành trong tương lai".

Ngón tay đan chéo.


Xin vui lòng tránh thêm ý kiến ​​như câu trả lời.
Karthick Ramesh

0

Nó được giải quyết trong iOS 13.3. Chỉ cần cập nhật hệ điều hành và xCode của bạn.


1
Xcode 11.3 (11C29) vào ngày 10.15.2 dẫn đến một hành vi khác đối với tôi: Điều hướng ngược đang hoạt động, nhưng sau đó, NavigationLink không còn chức năng nữa. Nhấp vào nó không làm gì cả.
malte

@malte Tốt hơn là mở một câu hỏi mới cho điều đó. Trước khi tôi kiểm tra mã của bạn, hãy cung cấp công cụ .buttonStyle(PlainButtonStyle())sửa đổi NavigationLink của bạn và thử lại. cho tôi biết nếu bạn hỏi một câu hỏi
THỨ SÁU 23/12/19

1
Bạn đúng. Hóa ra đã có một câu hỏi mới: stackoverflow.com/questions/59279176/ory
malte
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.