Навигация в macOS SwiftUI для единого представления
Я пытаюсь создать представление настроек для своего приложения строки состояния macOS SwiftUI. В моей реализации до сих пор использовалсяNavigationView
, а также NavigationLink
, но это решение создает половину представления, поскольку представление настроек отодвигает родительский вид в сторону. Снимок экрана и пример кода ниже.
Снимок экрана - боковая панель навигации
struct ContentView: View {
var body: some View {
VStack{
NavigationView{
NavigationLink(destination: SecondView()){
Text("Go to next view")
}}
}.frame(width: 800, height: 600, alignment: .center)}
}
struct SecondView: View {
var body: some View {
VStack{
Text("This is the second view")
}.frame(width: 800, height: 600, alignment: .center)
}
}
Небольшая информация, которую я могу найти, предполагает, что это неизбежно при использовании SwiftUI в macOS, потому что `` полный экран '' NavigationView
на iOS (StackNavigationViewStyle
) недоступен в macOS.
Есть ли простой или даже сложный способ реализовать переход к представлению настроек, которое занимает весь кадр в SwiftUI для macOS? А если нет, можно ли использоватьAppKit
вызвать объект View, написанный на SwiftUI?
Также новичок в Swift - будьте осторожны.
5 ответов
Вот простая демонстрация возможного подхода к пользовательскому решению, похожему на навигацию. Протестировано с Xcode 11.4 / macOS 10.15.4
Примечание: для лучшей видимости используются цвета фона.
struct ContentView: View {
@State private var show = false
var body: some View {
VStack{
if !show {
RootView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if show {
NextView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
.transition(AnyTransition.move(edge: .trailing)).animation(.default)
}
}
}
}
struct RootView: View {
@Binding var show: Bool
var body: some View {
VStack{
Button("Next") { self.show = true }
Text("This is the first view")
}
}
}
struct NextView: View {
@Binding var show: Bool
var body: some View {
VStack{
Button("Back") { self.show = false }
Text("This is the second view")
}
}
}
Я расширил замечательное предложение Asperi и создал универсальный многоразовыйStackNavigationView
для macOS (или даже iOS, если хотите). Некоторые основные моменты:
- Он поддерживает любое количество подпредставлений (в любом макете).
- Он автоматически добавляет кнопку "Назад" для каждого подпредставления (пока только текст, но вы можете поменять местами значок, если используете macOS 11+).
Swift v5.2:
struct StackNavigationView<RootContent, SubviewContent>: View where RootContent: View, SubviewContent: View {
@Binding var currentSubviewIndex: Int
@Binding var showingSubview: Bool
let subviewByIndex: (Int) -> SubviewContent
let rootView: () -> RootContent
var body: some View {
VStack {
VStack{
if !showingSubview { // Root view
rootView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if showingSubview { // Correct subview for current index
StackNavigationSubview(isVisible: self.$showingSubview) {
self.subviewByIndex(self.currentSubviewIndex)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(AnyTransition.move(edge: .trailing)).animation(.default)
}
}
}
}
init(currentSubviewIndex: Binding<Int>, showingSubview: Binding<Bool>, @ViewBuilder subviewByIndex: @escaping (Int) -> SubviewContent, @ViewBuilder rootView: @escaping () -> RootContent) {
self._currentSubviewIndex = currentSubviewIndex
self._showingSubview = showingSubview
self.subviewByIndex = subviewByIndex
self.rootView = rootView
}
private struct StackNavigationSubview<Content>: View where Content: View {
@Binding var isVisible: Bool
let contentView: () -> Content
var body: some View {
VStack {
HStack { // Back button
Button(action: {
self.isVisible = false
}) {
Text("< Back")
}.buttonStyle(BorderlessButtonStyle())
Spacer()
}
.padding(.horizontal).padding(.vertical, 4)
contentView() // Main view content
}
}
}
}
Больше информации на @ViewBuilder
и используемые дженерики можно найти здесь.
Вот базовый пример его использования. Родительский вид отслеживает текущий выбор и состояние отображения (используя@State
), позволяя чему-либо внутри его подпредставлений вызывать изменения состояния.
struct ExampleView: View {
@State private var currentSubviewIndex = 0
@State private var showingSubview = false
var body: some View {
StackNavigationView(
currentSubviewIndex: self.$currentSubviewIndex,
showingSubview: self.$showingSubview,
subviewByIndex: { index in
self.subView(forIndex: index)
}
) {
VStack {
Button(action: { self.showSubview(withIndex: 0) }) {
Text("Show View 1")
}
Button(action: { self.showSubview(withIndex: 1) }) {
Text("Show View 2")
}
Button(action: { self.showSubview(withIndex: 2) }) {
Text("Show View 3")
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
}
}
private func subView(forIndex index: Int) -> AnyView {
switch index {
case 0: return AnyView(Text("I'm View One").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.green))
case 1: return AnyView(Text("I'm View Two").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.yellow))
case 2: return AnyView(VStack {
Text("And I'm...")
Text("View Three")
}.frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.orange))
default: return AnyView(Text("Inavlid Selection").frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red))
}
}
private func showSubview(withIndex index: Int) {
currentSubviewIndex = index
showingSubview = true
}
}
Примечание. Для подобных универсальных шаблонов необходимо, чтобы все вложенные представления были одного типа. Если это не так, вы можете обернуть ихAnyView
, как я сделал здесь. ВAnyView
оболочка не требуется, если вы используете согласованный тип для всех вложенных представлений (тип корневого представления не обязательно должен совпадать).
Эй, у меня была проблема, что я хотел иметь несколько слоев navigationView, я не уверен, что это тоже ваша попытка, но если это так: MacOS НЕ наследует NavigationView. Это означает, что вам необходимо предоставить свой DetailView (или
SecondView
в вашем случае) со своим
NavigationView
. Итак, просто вставляем как
[...], destination: NavigationView { SecondView() }) [...]
должен сделать свое дело.
Но осторожно! То же самое для целей iOS приведет к неожиданному поведению. Итак, если вы нацелены на оба, убедитесь, что вы используете
#if os(macOS)
!
Однако при создании представления настроек я бы порекомендовал вам также заглянуть в сцену настроек, предоставленную Apple.
Вы можете получить полноэкранную навигацию с
.navigationViewStyle(StackNavigationViewStyle())