簡體   English   中英

SwiftUI 中 NavigationView 導航欄的自定義后退按鈕

[英]Custom back button for NavigationView's navigation bar in SwiftUI

我想添加一個看起來像這樣的自定義導航按鈕:

所需的導航返回按鈕

現在,我為此編寫了一個自定義BackButton視圖。 將該視圖應用為前導導航欄項目時,通過執行以下操作:

.navigationBarItems(leading: BackButton())

...導航視圖如下所示:

當前導航返回按鈕

我玩過以下修飾符:

.navigationBarItem(title: Text(""), titleDisplayMode: .automatic, hidesBackButton: true)

沒有任何運氣。

問題

我怎樣才能...

  1. 在導航欄中設置用作自定義后退按鈕的視圖? 或者:
  2. 以編程方式將視圖彈出回其父級?
    當采用這種方法時,我可以使用.navigationBarHidden(true)完全隱藏導航欄

TL;博士

使用它來轉換您的視圖:

NavigationLink(destination: SampleDetails()) {}

將此添加到視圖本身:

@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

然后,在按鈕操作或其他內容中,關閉視圖:

presentationMode.wrappedValue.dismiss()

完整代碼

從父級,使用NavigationLink

 NavigationLink(destination: SampleDetails()) {}

在 DetailsView 中隱藏navigationBarBackButton並將自定義后退按鈕設置為前導navigationBarItem

struct SampleDetails: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var btnBack : some View { Button(action: {
        self.presentationMode.wrappedValue.dismiss()
        }) {
            HStack {
            Image("ic_back") // set image here
                .aspectRatio(contentMode: .fit)
                .foregroundColor(.white)
                Text("Go back")
            }
        }
    }
    
    var body: some View {
            List {
                Text("sample code")
        }
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: btnBack)
    }
}

SwiftUI 1.0

看起來您現在可以結合使用navigationBarBackButtonHidden.navigationBarItems來獲得您想要達到的效果。

代碼

struct Navigation_CustomBackButton_Detail: View {
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View {
        ZStack {
            Color("Theme3BackgroundColor")
            VStack(spacing: 25) {
                Image(systemName: "globe").font(.largeTitle)
                Text("NavigationView").font(.largeTitle)
                Text("Custom Back Button").foregroundColor(.gray)
                HStack {
                    Image("NavBarBackButtonHidden")
                    Image(systemName: "plus")
                    Image("NavBarItems")
                }
                Text("Hide the system back button and then use the navigation bar items modifier to add your own.")
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color("Theme3ForegroundColor"))
                    .foregroundColor(Color("Theme3BackgroundColor"))
                
                Spacer()
            }
            .font(.title)
            .padding(.top, 50)
        }
        .navigationBarTitle(Text("Detail View"), displayMode: .inline)
        .edgesIgnoringSafeArea(.bottom)
        // Hide the system back button
        .navigationBarBackButtonHidden(true)
        // Add your custom back button here
        .navigationBarItems(leading:
            Button(action: {
                self.presentationMode.wrappedValue.dismiss()
            }) {
                HStack {
                    Image(systemName: "arrow.left.circle")
                    Text("Go Back")
                }
        })
    }
}

例子

這是它的樣子(摘自“SwiftUI Views”一書): SwiftUI 視圖書摘

基於此處的其他答案,這是選項 2 在 XCode 11.0 中為我工作的簡化答案:

struct DetailView: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var body: some View {

        Button(action: {
           self.presentationMode.wrappedValue.dismiss()
        }) {
            Image(systemName: "gobackward").padding()
        }
        .navigationBarHidden(true)

    }
}

注意:要隱藏 NavigationBar,我還需要在 ContentView 中設置並隱藏 NavigationBar。

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: DetailView()) {
                    Text("Link").padding()
                }
            } // Main VStack
            .navigationBarTitle("Home")
            .navigationBarHidden(true)

        } //NavigationView
    }
}

這是一個更精簡的版本,它使用其他評論中顯示的原則僅更改按鈕的文本。 chevron.left圖標也可以很容易地替換為另一個圖標。

創建您自己的按鈕,然后使用 .navigationBarItems() 分配它。 我發現以下格式最接近默認的后退按鈕。

    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var backButton : some View {
        Button(action: {
            self.presentationMode.wrappedValue.dismiss()
        }) {
            HStack(spacing: 0) {
                Image(systemName: "chevron.left")
                    .font(.title2)
                Text("Cancel")
            }
        }
    }

確保使用.navigationBarBackButtonHidden(true)隱藏默認按鈕並將其替換為您自己的!

        List(series, id:\.self, selection: $selection) { series in
            Text(series.SeriesLabel)
        }
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: backButton)

我希望您想在所有可導航屏幕中使用自定義后退按鈕,所以我根據@Ashish 答案編寫了自定義包裝器。

struct NavigationItemContainer<Content>: View where Content: View {
    private let content: () -> Content
    @Environment(\.presentationMode) var presentationMode

    private var btnBack : some View { Button(action: {
        self.presentationMode.wrappedValue.dismiss()
    }) {
        HStack {
            Image("back_icon") // set image here
                .aspectRatio(contentMode: .fit)
                .foregroundColor(.black)
            Text("Go back")
        }
        }
    }

    var body: some View {
        content()
            .navigationBarBackButtonHidden(true)
            .navigationBarItems(leading: btnBack)
    }

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }
}

在 NavigationItemContainer 中包裝屏幕內容:

用法:

struct CreateAccountScreenView: View {
    var body: some View {
        NavigationItemContainer {
            VStack(spacing: 21) {
                AppLogoView()
                //...
            }
        }
    }
}

滑動不會以這種方式被禁用。

為我工作。 XCode 11.3.1

把它放在你的根視圖中

init() {
    UINavigationBar.appearance().isUserInteractionEnabled = false
    UINavigationBar.appearance().backgroundColor = .clear
    UINavigationBar.appearance().barTintColor = .clear
    UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
    UINavigationBar.appearance().shadowImage = UIImage()
    UINavigationBar.appearance().tintColor = .clear
}

這在你的孩子視圖中

@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

Button(action: {self.presentationMode.wrappedValue.dismiss()}) {
    Image(systemName: "gobackward")
}

您可以為此使用UIAppearance

if let image = UIImage(named: "back-button") {
    UINavigationBar.appearance().backIndicatorImage = image
    UINavigationBar.appearance().backIndicatorTransitionMaskImage = image
}

這應該在您的應用程序中盡早添加,例如App.init 這也保留了本機向后滑動功能。

我在這里看到的所有解決方案似乎都禁用了滑動返回功能以導航到上一頁,因此分享我發現的維護該功能的解決方案。 您可以擴展您的根視圖並覆蓋您的導航樣式並在視圖初始化程序中調用該函數。

示例視圖

struct SampleRootView: View {

    init() {
        overrideNavigationAppearance()
    }

    var body: some View {
        Text("Hello, World!")
    }
}

擴大

extension SampleRootView {
   func overrideNavigationAppearance() {
        let navigationBarAppearance = UINavigationBarAppearance()
        let barAppearace = UINavigationBar.appearance()
        barAppearace.tintColor = *desired UIColor for icon*
        barAppearace.barTintColor = *desired UIColor for icon*

        navigationBarAppearance.setBackIndicatorImage(*desired UIImage for custom icon*, transitionMaskImage: *desired UIImage for custom icon*)

        UINavigationBar.appearance().standardAppearance = navigationBarAppearance
        UINavigationBar.appearance().compactAppearance = navigationBarAppearance
        UINavigationBar.appearance().scrollEdgeAppearance = navigationBarAppearance
   }
}

這種方法的唯一缺點是我還沒有找到刪除/更改與自定義后退按鈕關聯的文本的方法。

此解決方案適用於 iPhone。 但是,對於 iPad,由於 splitView,它不會工作。

import SwiftUI

struct NavigationBackButton: View {
  var title: Text?
  @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>

  var body: some View {
    ZStack {
      VStack {
        ZStack {
          HStack {
            Button(action: {
              self.presentationMode.wrappedValue.dismiss()
            }) {
              Image(systemName: "chevron.left")
                .font(.title)
                .frame(width: 44, height: 44)
              title
            }
            Spacer()
          }
        }
        Spacer()
      }
    }
    .zIndex(1)
    .navigationBarTitle("")
    .navigationBarHidden(true)
  }
}

struct NavigationBackButton_Previews: PreviewProvider {
  static var previews: some View {
    NavigationBackButton()
  }
}

真的很簡單的方法。 只有兩行代碼🔥

@Environment(\.presentationMode) var presentationMode
self.presentationMode.wrappedValue.dismiss()

例子:

import SwiftUI

struct FirstView: View {
    @State var showSecondView = false
    
    var body: some View {
        NavigationLink(destination: SecondView(),isActive : self.$showSecondView){
            Text("Push to Second View")
        }
    }
}


struct SecondView : View{
    @Environment(\.presentationMode) var presentationMode

    var body : some View {    
        Button(action:{ self.presentationMode.wrappedValue.dismiss() }){
            Text("Go Back")    
        }    
    }
}

我發現了這個: https ://ryanashcraft.me/swiftui-programmatic-navigation/

它確實有效,並且可能為狀態機控制顯示的內容奠定基礎,但它不像以前那樣簡單。

import Combine
import SwiftUI

struct DetailView: View {
    var onDismiss: () -> Void

    var body: some View {
        Button(
            "Here are details. Tap to go back.",
            action: self.onDismiss
        )
    }
}

struct RootView: View {
    var link: NavigationDestinationLink<DetailView>
    var publisher: AnyPublisher<Void, Never>

    init() {
        let publisher = PassthroughSubject<Void, Never>()
        self.link = NavigationDestinationLink(
            DetailView(onDismiss: { publisher.send() }),
            isDetail: false
        )
        self.publisher = publisher.eraseToAnyPublisher()
    }

    var body: some View {
        VStack {
            Button("I am root. Tap for more details.", action: {
                self.link.presented?.value = true
            })
        }
            .onReceive(publisher, perform: { _ in
                self.link.presented?.value = false
            })
    }
}

struct ContentView: View {
    var body: some View {
        NavigationView {
            RootView()
        }
    }
}

If you want to hide the button then you can replace the DetailView with this:

struct LocalDetailView: View {
    var onDismiss: () -> Void

    var body: some View {
        Button(
            "Here are details. Tap to go back.",
            action: self.onDismiss
        )
            .navigationBarItems(leading: Text(""))
    }
}

寫這個:

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {

        }.onAppear() {
            UINavigationBar.appearance().tintColor = .clear
            UINavigationBar.appearance().backIndicatorImage = UIImage(named: "back")?.withRenderingMode(.alwaysOriginal)
            UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "back")?.withRenderingMode(.alwaysOriginal)
        }
    }
}

在 iOS 14+ 上,使用presentationMode變量實際上非常容易

在此示例NewItemView將在addItem完成時被解雇:

struct NewItemView: View {
    @State private var itemDescription:String = ""
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    
    
    var body: some View {
        VStack {
            TextEditor(text: $itemDescription)
        }.onTapGesture {
            hideKeyboard()
        }.toolbar {
            
            ToolbarItem {
                Button(action: addItem){
                    Text("Save")
                }
            }
            
        }.navigationTitle("Add Question")
        
    }
    private func addItem() {
        // Add save logic
        // ...
        
        // Dismiss on complete
        presentationMode.wrappedValue.dismiss()
    }
    
    private func hideKeyboard() {
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

struct NewItemView_Previews: PreviewProvider {
    static var previews: some View {
        NewItemView()
    }
}

如果您需要父(主)視圖:

struct SampleMainView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \DbQuestion.timestamp, ascending: true)],
        animation: .default)
    private var items: FetchedResults<Item>
    
    var body: some View {
        NavigationView {
            List {
                ForEach(items) { item in
                    NavigationLink {
                        Text("This is item detail page")
                    } label: {
                        Text("Item at \(item.id)")
                    }
                }
                
            }
            .toolbar {
                ToolbarItem {
                        // Creates a button on toolbar
                        NavigationLink {
                            // New Item Page
                            NewItemView()
                        } label: {
                            Text("Add item")
                        }
                }
                ToolbarItem(placement: .navigationBarTrailing) {
                    EditButton()
                }
            }.navigationTitle("Main Screen")
            
        }
    }
}

iOS 15

presentationMode.wrappedValue.dismiss()現在已棄用。

它被DismissAction取代

private struct SheetContents: View {
    @Environment(\.dismiss) private var dismiss

    var body: some View {
        Button("Done") {
            dismiss()
        }
    }
}

您仍然以相同的方式創建自定義后退按鈕。

struct NavBackButton: View {
    let dismiss: DismissAction
    
    var body: some View {
        Button {
            dismiss()
        } label: {
            Image("...custom back button here")
        }
    }
}

將其附加到您的視圖中。

.navigationBarBackButtonHidden(true) // Hide default button
.navigationBarItems(leading: NavBackButton(dismiss: self.dismiss)) // Attach custom button

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM