我正在尝试构建自定义 NavigationView,并且正在努力解决如何实现自定义“.navigationBarItems(前导:/ *插入视图/,尾随:/插入视图*/)”。我认为我必须使用preferenceKey,但我不知道如何让它接受视图。
我的顶部菜单看起来像这样:
import SwiftUI
struct TopMenu<Left: View, Right: View>: View {
let leading: Left
let trailing: Right
init(@ViewBuilder left: @escaping () -> Left, @ViewBuilder right: @escaping () -> Right) {
self.leading = left()
self.trailing = right()
}
var body: some View {
VStack(spacing: 0) {
HStack {
leading
Spacer()
trailing
}.frame(height: 30, alignment: .center)
Spacer()
}
.padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
}
}
struct TopMenu_Previews: PreviewProvider {
static var previews: some View {
TopMenu(left: { }, right: { })
}
}
Run Code Online (Sandbox Code Playgroud)
这是我尝试创建一个preferenceKey来更新它,我显然错过了一些非常基本的东西:
struct TopMenuItemsLeading: PreferenceKey {
static var defaultValue:View
static func reduce(value: inout View, nextValue: () -> View) {
value = nextValue()
}
}
struct TopMenuItemsTrailing: PreferenceKey {
static var defaultValue:View
static func reduce(value: inout View, nextValue: () -> View) {
value = nextValue()
}
}
extension View {
func topMenuItems(leading: View, trailing: View) -> some View {
self.preference(key: TopMenuItemsLeading.self, value: leading)
self.preference(key: TopMenuItemsTrailing.self, value: trailing)
}
}
Run Code Online (Sandbox Code Playgroud)
lmu*_*nck 11
好的,这里有一些很好的部分答案,但没有一个真正实现了我所要求的,即使用首选项键将视图传递到视图层次结构。本质上是 .navigationBarItems 方法正在做的事情,但是使用我自己的自定义视图。
然而,我找到了一个解决方案,所以这里是(如果我错过了任何明显的捷径,我深表歉意。这是我第一次使用preferenceKeys做任何事情):
import SwiftUI
struct TopMenu: View {
@State private var show:Bool = false
var body: some View {
VStack {
TopMenuView {
Button("Change", action: { show.toggle() })
Text("Hello world!")
.topMenuItems(leading: Image(systemName: show ? "xmark.circle" : "pencil"))
.topMenuItems(trailing: Image(systemName: show ? "pencil" : "xmark.circle"))
}
}
}
}
struct TopMenu_Previews: PreviewProvider {
static var previews: some View {
TopMenu()
}
}
/*
To emulate .navigationBarItems(leading: View, trailing: View), I need four things:
1) EquatableViewContainer - Because preferenceKeys need to be equatable to be able to update when a change occurred
2) PreferenceKeys - That use the EquatableViewContainer for both leading and trailing views
3) ViewExtenstions - That allow us to set the preferenceKeys individually or one at a time
4) TopMenu view - That we can set somewhere higher in the view hierarchy.
*/
// First, create an EquatableViewContainer we can use as preferenceKey data
struct EquatableViewContainer: Equatable {
let id = UUID().uuidString
let view:AnyView
static func == (lhs: EquatableViewContainer, rhs: EquatableViewContainer) -> Bool {
return lhs.id == rhs.id
}
}
// Second, define preferenceKeys that uses the Equatable view container
struct TopMenuItemsLeading: PreferenceKey {
static var defaultValue: EquatableViewContainer = EquatableViewContainer(view: AnyView(EmptyView()) )
static func reduce(value: inout EquatableViewContainer, nextValue: () -> EquatableViewContainer) {
value = nextValue()
}
}
struct TopMenuItemsTrailing: PreferenceKey {
static var defaultValue: EquatableViewContainer = EquatableViewContainer(view: AnyView(EmptyView()) )
static func reduce(value: inout EquatableViewContainer, nextValue: () -> EquatableViewContainer) {
value = nextValue()
}
}
// Third, create view-extensions for each of the ways to modify the TopMenu
extension View {
// Change only leading view
func topMenuItems<LView: View>(leading: LView) -> some View {
self
.preference(key: TopMenuItemsLeading.self, value: EquatableViewContainer(view: AnyView(leading)))
}
// Change only trailing view
func topMenuItems<RView: View>(trailing: RView) -> some View {
self
.preference(key: TopMenuItemsTrailing.self, value: EquatableViewContainer(view: AnyView(trailing)))
}
// Change both leading and trailing views
func topMenuItems<LView: View, TView: View>(leading: LView, trailing: TView) -> some View {
self
.preference(key: TopMenuItemsLeading.self, value: EquatableViewContainer(view: AnyView(leading)))
}
}
// Fourth, create the view for the TopMenu
struct TopMenuView<Content: View>: View {
// Content to put into the menu
let content: Content
@State private var leading:EquatableViewContainer = EquatableViewContainer(view: AnyView(EmptyView()))
@State private var trailing:EquatableViewContainer = EquatableViewContainer(view: AnyView(EmptyView()))
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content()
}
var body: some View {
VStack(spacing: 0) {
ZStack {
HStack {
leading.view
Spacer()
trailing.view
}
Text("TopMenu").fontWeight(.black)
}
.padding(EdgeInsets(top: 0, leading: 2, bottom: 5, trailing: 2))
.background(Color.gray.edgesIgnoringSafeArea(.top))
content
Spacer()
}
.onPreferenceChange(TopMenuItemsLeading.self, perform: { value in
leading = value
})
.onPreferenceChange(TopMenuItemsTrailing.self, perform: { value in
trailing = value
})
}
}
`````
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1518 次 |
| 最近记录: |