基于 sheet 视图呈现 2 个选择的 SwiftUI 导航决策
SwiftUI navigation decision based on a sheet view presenting 2 choice
我正在展示一个“向导”,它将检测 BLE 设备,如果它是正确的,最后一个视图将询问我们是要注册还是跳过。
编辑:{
查看顺序是:MainView 在 fullScreenCover 中呈现第一个信息视图,通知如何检测 BLE 设备然后这个视图推送第二个视图,其中包含最近的 BLE 设备上的一些信息,正是在这个视图中,我们有我所在的分支呈现 sheet 询问用户是否要继续并注册 BLE 设备或跳过。
So MAIN > INFOView -> BLE detection (> Register or skip ? RegisterView : Destack to main)
}
我看到最后一个视图显示为 sheet 它有 2 个按钮,提到的第一个是“注册”,另一个是“跳过”。如果用户按下注册键,那么我们将关闭 sheet 并导航到正在收集个人信息以注册 BLE 设备的视图。另一方面,如果用户选择跳过,则向导需要取消堆叠回到主视图。
通常在 UIKit 中,如果选择了跳过,我只会让一个代表通知我选择。我会调用 pop 到根视图控制器,否则,如果选择了注册选项,我会关闭 sheet 视图,然后导航到另一个最终视图并让用户注册。
在 SwiftUI 中,我不知道如何处理那个导航叉。我尝试使用 PassthroughSubject,但后来我必须将 PassthroughSubject var 设置为状态 var,最后,我只是没有收到来自发送选择的回电。
尝试绑定然后希望创建一个 onReceive,但后来它要求一个发布者并且为此创建一个发布者感觉是错误的。
我想知道在 .swiftUI 中处理这个问题的最佳方法是什么?
编辑:
这是显示 BLE 设备(智能自行车)信息的视图的代码(更新了来自@Predrag Samardzic 的重播),并首先推送一个请求,了解用户是否想要注册,如果是如果没有关闭整个堆栈,请推送该注册屏幕。
struct A18BikeDiscoveryView: View {
@EnvironmentObject var bleManager: ArgonBLEManager
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
private let shouldShowRegistration = CurrentValueSubject<Bool, Never>(false)
@State var isSheetPresented = false
@State var isRegistrationPresented = false
var body: some View {
VStack{
NavigationLink(
destination: A18RegistrationQuestionairy(QuestionairyViewModel()),
isActive: $isRegistrationPresented
) {
EmptyView()
}
A18ImageTextBanner(text: NSLocalizedString("bike_discovery_view_title", comment: ""))
.padding(.bottom, 35)
.navigationBarBackButtonHidden(true)
if let value = bleManager.model?.bikeInfo?.bikeModel{
Text(value)
.fontWeight(.bold)
.scaledFont(.largeTitle)
}
Image("subitoBike")
.resizable()
.frame(minWidth: 0334, idealWidth: 334, maxWidth: .infinity, minHeight: 223, idealHeight: 223, maxHeight: .infinity, alignment: .center)
.aspectRatio(contentMode: .fit)
.padding(.bottom, 10)
Divider()
VStack(alignment: .leading){
HStack{
Text("bike_discovery_view_year_created")
if let v = bleManager.model?.bikeInfo?.year{
Text(v)
}
}
HStack{
Text("bike_discovery_view_model_size")
Text("\(getSizeFromSerial())")
}
HStack{
Text("bike_discovery_view_bike_serial_number")
if let v = bleManager.model?.bikeInfo?.bikeSerialNumber {
Text(v)
}
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 66, alignment: .leading)
.padding(.horizontal, 40)
Divider()
.padding(.bottom, 30)
Button(action: {
isSheetPresented = true
}, label: {
Text("bike_discovery_view_bike_pairing_button_title")
.fontWeight(.bold)
.foregroundColor(.white)
})
.buttonStyle(A18RoundButtonStyle(bgColor: .red))
.padding(.horizontal)
.sheet(
isPresented: $isSheetPresented,
onDismiss: {
if shouldShowRegistration.value {
isRegistrationPresented = true
}},
content: {
A18BikeParingSelection(shouldShowRegistration: shouldShowRegistration)
})
.onReceive(shouldShowRegistration) { shouldShowRegistration in
isSheetPresented = false
}
Button(action: {
bleManager.disconect()
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("bike_discovery_view_bike_pairing_cancel_button_title")
.fontWeight(.bold)
.foregroundColor(Color("grey55"))
})
.padding()
Spacer()
}
.navigationBarColor(backgroundColor: .white, tintColor: .black)
.navigationBarTitleDisplayMode(.inline)
}
func getSizeFromSerial() -> String {
if let serial = bleManager.model?.bikeInfo?.bikeSerialNumber {
if serial.contains("XXS"){
return "XXS"
}else if serial.contains("XSM") {
return "XS"
}else if serial.contains("SML"){
return "S"
}else if serial.contains("MED"){
return "M"
}else if serial.contains("LAR"){
return "L"
}
}
return "N/A"
}
}
这是一种可能的解决方案 - 使用 CurrentValueSubject 以触发关闭并保留有关在显示的屏幕上所做的选择的信息。然后,如果需要注册,则在 sheet 被关闭时触发它。
struct MainView: View {
private let shouldShowRegistration = CurrentValueSubject<Bool, Never>(false)
@State var isSheetPresented = false
@State var isRegistrationPresented = false
var body: some View {
VStack {
// this part is if you want to push registration screen, you will need to have MainView inside NavigationView for it
NavigationLink(
destination: RegistrationView(),
isActive: $isRegistrationPresented
) {
EmptyView()
}
// ----------------------------------------------------
Button {
isSheetPresented = true
} label: {
Text("Present sheet")
}
.sheet(
isPresented: $isSheetPresented,
onDismiss: {
if shouldShowRegistration.value {
isRegistrationPresented = true
}},
content: {
ChoiceView(shouldShowRegistration: shouldShowRegistration)
})
.onReceive(shouldShowRegistration) { shouldShowRegistration in
isSheetPresented = false
}
// this part is if you want to present registration screen as sheet
// .sheet(
// isPresented: $isRegistrationPresented,
// content: {
// RegistrationView()
// })
}
}
}
struct ChoiceView: View {
let shouldShowRegistration: CurrentValueSubject<Bool, Never>
var body: some View {
VStack{
Button {
shouldShowRegistration.send(false)
} label: {
Text("Dismiss")
}
Button {
shouldShowRegistration.send(true)
} label: {
Text("Register")
}
}
}
}
struct RegistrationView: View {
var body: some View {
Text("Registration")
}
}
我正在展示一个“向导”,它将检测 BLE 设备,如果它是正确的,最后一个视图将询问我们是要注册还是跳过。
编辑:{ 查看顺序是:MainView 在 fullScreenCover 中呈现第一个信息视图,通知如何检测 BLE 设备然后这个视图推送第二个视图,其中包含最近的 BLE 设备上的一些信息,正是在这个视图中,我们有我所在的分支呈现 sheet 询问用户是否要继续并注册 BLE 设备或跳过。
So MAIN > INFOView -> BLE detection (> Register or skip ? RegisterView : Destack to main) }
我看到最后一个视图显示为 sheet 它有 2 个按钮,提到的第一个是“注册”,另一个是“跳过”。如果用户按下注册键,那么我们将关闭 sheet 并导航到正在收集个人信息以注册 BLE 设备的视图。另一方面,如果用户选择跳过,则向导需要取消堆叠回到主视图。
通常在 UIKit 中,如果选择了跳过,我只会让一个代表通知我选择。我会调用 pop 到根视图控制器,否则,如果选择了注册选项,我会关闭 sheet 视图,然后导航到另一个最终视图并让用户注册。
在 SwiftUI 中,我不知道如何处理那个导航叉。我尝试使用 PassthroughSubject,但后来我必须将 PassthroughSubject var 设置为状态 var,最后,我只是没有收到来自发送选择的回电。
尝试绑定然后希望创建一个 onReceive,但后来它要求一个发布者并且为此创建一个发布者感觉是错误的。
我想知道在 .swiftUI 中处理这个问题的最佳方法是什么?
编辑: 这是显示 BLE 设备(智能自行车)信息的视图的代码(更新了来自@Predrag Samardzic 的重播),并首先推送一个请求,了解用户是否想要注册,如果是如果没有关闭整个堆栈,请推送该注册屏幕。
struct A18BikeDiscoveryView: View {
@EnvironmentObject var bleManager: ArgonBLEManager
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
private let shouldShowRegistration = CurrentValueSubject<Bool, Never>(false)
@State var isSheetPresented = false
@State var isRegistrationPresented = false
var body: some View {
VStack{
NavigationLink(
destination: A18RegistrationQuestionairy(QuestionairyViewModel()),
isActive: $isRegistrationPresented
) {
EmptyView()
}
A18ImageTextBanner(text: NSLocalizedString("bike_discovery_view_title", comment: ""))
.padding(.bottom, 35)
.navigationBarBackButtonHidden(true)
if let value = bleManager.model?.bikeInfo?.bikeModel{
Text(value)
.fontWeight(.bold)
.scaledFont(.largeTitle)
}
Image("subitoBike")
.resizable()
.frame(minWidth: 0334, idealWidth: 334, maxWidth: .infinity, minHeight: 223, idealHeight: 223, maxHeight: .infinity, alignment: .center)
.aspectRatio(contentMode: .fit)
.padding(.bottom, 10)
Divider()
VStack(alignment: .leading){
HStack{
Text("bike_discovery_view_year_created")
if let v = bleManager.model?.bikeInfo?.year{
Text(v)
}
}
HStack{
Text("bike_discovery_view_model_size")
Text("\(getSizeFromSerial())")
}
HStack{
Text("bike_discovery_view_bike_serial_number")
if let v = bleManager.model?.bikeInfo?.bikeSerialNumber {
Text(v)
}
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 66, alignment: .leading)
.padding(.horizontal, 40)
Divider()
.padding(.bottom, 30)
Button(action: {
isSheetPresented = true
}, label: {
Text("bike_discovery_view_bike_pairing_button_title")
.fontWeight(.bold)
.foregroundColor(.white)
})
.buttonStyle(A18RoundButtonStyle(bgColor: .red))
.padding(.horizontal)
.sheet(
isPresented: $isSheetPresented,
onDismiss: {
if shouldShowRegistration.value {
isRegistrationPresented = true
}},
content: {
A18BikeParingSelection(shouldShowRegistration: shouldShowRegistration)
})
.onReceive(shouldShowRegistration) { shouldShowRegistration in
isSheetPresented = false
}
Button(action: {
bleManager.disconect()
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("bike_discovery_view_bike_pairing_cancel_button_title")
.fontWeight(.bold)
.foregroundColor(Color("grey55"))
})
.padding()
Spacer()
}
.navigationBarColor(backgroundColor: .white, tintColor: .black)
.navigationBarTitleDisplayMode(.inline)
}
func getSizeFromSerial() -> String {
if let serial = bleManager.model?.bikeInfo?.bikeSerialNumber {
if serial.contains("XXS"){
return "XXS"
}else if serial.contains("XSM") {
return "XS"
}else if serial.contains("SML"){
return "S"
}else if serial.contains("MED"){
return "M"
}else if serial.contains("LAR"){
return "L"
}
}
return "N/A"
}
}
这是一种可能的解决方案 - 使用 CurrentValueSubject 以触发关闭并保留有关在显示的屏幕上所做的选择的信息。然后,如果需要注册,则在 sheet 被关闭时触发它。
struct MainView: View {
private let shouldShowRegistration = CurrentValueSubject<Bool, Never>(false)
@State var isSheetPresented = false
@State var isRegistrationPresented = false
var body: some View {
VStack {
// this part is if you want to push registration screen, you will need to have MainView inside NavigationView for it
NavigationLink(
destination: RegistrationView(),
isActive: $isRegistrationPresented
) {
EmptyView()
}
// ----------------------------------------------------
Button {
isSheetPresented = true
} label: {
Text("Present sheet")
}
.sheet(
isPresented: $isSheetPresented,
onDismiss: {
if shouldShowRegistration.value {
isRegistrationPresented = true
}},
content: {
ChoiceView(shouldShowRegistration: shouldShowRegistration)
})
.onReceive(shouldShowRegistration) { shouldShowRegistration in
isSheetPresented = false
}
// this part is if you want to present registration screen as sheet
// .sheet(
// isPresented: $isRegistrationPresented,
// content: {
// RegistrationView()
// })
}
}
}
struct ChoiceView: View {
let shouldShowRegistration: CurrentValueSubject<Bool, Never>
var body: some View {
VStack{
Button {
shouldShowRegistration.send(false)
} label: {
Text("Dismiss")
}
Button {
shouldShowRegistration.send(true)
} label: {
Text("Register")
}
}
}
}
struct RegistrationView: View {
var body: some View {
Text("Registration")
}
}