gpt4 book ai didi

SwiftUI 将 TupleView 转换为 AnyView 数组

转载 作者:行者123 更新时间:2023-12-04 00:59:43 28 4
gpt4 key购买 nike

代码

我有以下代码:

struct CustomTabView: View where Content: View {

let children: [AnyView]

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

let m = Mirror(reflecting: content())
if let value = m.descendant("value") {
let tupleMirror = Mirror(reflecting: value)
let tupleElements = tupleMirror.children.map({ AnyView($0.value) }) // ERROR
self.children = tupleElements
} else {
self.children = [AnyView]()
}
}

var body: some View {
ForEach(self.children) { child in
child...
}
}
}

问题

我正在尝试转换 TupleViewAnyView 的数组但我收到错误
Protocol type 'Any' cannot conform to 'View' because only concrete types can conform to protocols

可能的解决方案

我可以解决这个问题的一种方法是将类型删除的 View 传入 CustomTabView像这样:

CustomTabView {
AnyView(Text("A"))
AnyView(Text("B"))
AnyView(Rectangle())
}

理想情况下

但我希望能够像 native TabView 一样执行以下操作

CustomTabView {
Text("A")
Text("B")
Rectangle()
}

那么我将如何转换 TupleViewAnyView 的数组?

最佳答案

以下是我使用 SwiftUI 创建自定义选项卡 View 的方法:

struct CustomTabView<Content>: View where Content: View {

@State private var currentIndex: Int = 0
@EnvironmentObject private var model: Model

let content: () -> Content

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

var body: some View {

GeometryReader { geometry in
return ZStack {
// pages
// onAppear on all pages are called only on initial load
self.pagesInHStack(screenGeometry: geometry)
}
.overlayPreferenceValue(CustomTabItemPreferenceKey.self) { preferences in
// tab bar
return self.createTabBar(screenGeometry: geometry, tabItems: preferences.map {TabItem(tag: $0.tag, tab: $0.item)})
}
}
}

func getTabBarHeight(screenGeometry: GeometryProxy) -> CGFloat {
// https://medium.com/@hacknicity/ipad-navigation-bar-and-toolbar-height-changes-in-ios-12-91c5766809f4
// ipad 50
// iphone && portrait 49
// iphone && portrait && bottom safety 83
// iphone && landscape 32
// iphone && landscape && bottom safety 53
if UIDevice.current.userInterfaceIdiom == .pad {
return 50 + screenGeometry.safeAreaInsets.bottom
} else if UIDevice.current.userInterfaceIdiom == .phone {
if !model.landscape {
return 49 + screenGeometry.safeAreaInsets.bottom
} else {
return 32 + screenGeometry.safeAreaInsets.bottom
}
}
return 50
}

func pagesInHStack(screenGeometry: GeometryProxy) -> some View {

let tabBarHeight = getTabBarHeight(screenGeometry: screenGeometry)
let heightCut = tabBarHeight - screenGeometry.safeAreaInsets.bottom
let spacing: CGFloat = 100 // so pages don't overlap (in case of leading and trailing safetyInset), arbitrary

return HStack(spacing: spacing) {
self.content()
// reduced height, so items don't appear under tha tab bar
.frame(width: screenGeometry.size.width, height: screenGeometry.size.height - heightCut)
// move up to cover the reduced height
// 0.1 for iPhone X's nav bar color to extend to status bar
.offset(y: -heightCut/2 - 0.1)
}
.frame(width: screenGeometry.size.width, height: screenGeometry.size.height, alignment: .leading)
.offset(x: -CGFloat(self.currentIndex) * screenGeometry.size.width + -CGFloat(self.currentIndex) * spacing)
}

func createTabBar(screenGeometry: GeometryProxy, tabItems: [TabItem]) -> some View {

let height = getTabBarHeight(screenGeometry: screenGeometry)

return VStack {
Spacer()
HStack(spacing: screenGeometry.size.width / (CGFloat(tabItems.count + 1) + 0.5)) {
Spacer()
ForEach(0..<tabItems.count, id: \.self) { i in
Group {
Button(action: {
self.currentIndex = i
}) {
tabItems[i].tab
}.foregroundColor(self.currentIndex == i ? .blue : .gray)
}
}
Spacer()
}
// move up from bottom safety inset
.padding(.bottom, screenGeometry.safeAreaInsets.bottom > 0 ? screenGeometry.safeAreaInsets.bottom - 5 : 0 )
.frame(width: screenGeometry.size.width, height: height)
.background(
self.getTabBarBackground(screenGeometry: screenGeometry)
)
}
// move down to cover bottom of new iphones and ipads
.offset(y: screenGeometry.safeAreaInsets.bottom)
}

func getTabBarBackground(screenGeometry: GeometryProxy) -> some View {

return GeometryReader { tabBarGeometry in
self.getBackgrounRectangle(tabBarGeometry: tabBarGeometry)
}
}

func getBackgrounRectangle(tabBarGeometry: GeometryProxy) -> some View {

return VStack {
Rectangle()
.fill(Color.white)
.opacity(0.8)
// border top
// https://www.reddit.com/r/SwiftUI/comments/dehx9t/how_to_add_border_only_to_bottom/
.padding(.top, 0.2)
.background(Color.gray)

.edgesIgnoringSafeArea([.leading, .trailing])
}
}
}

这是首选项和 View 扩展:

// MARK: - Tab Item Preference
struct CustomTabItemPreferenceData: Equatable {
var tag: Int
let item: AnyView
let stringDescribing: String // to let preference know when the tab item is changed
var badgeNumber: Int // to let preference know when the badgeNumber is changed


static func == (lhs: CustomTabItemPreferenceData, rhs: CustomTabItemPreferenceData) -> Bool {
lhs.tag == rhs.tag && lhs.stringDescribing == rhs.stringDescribing && lhs.badgeNumber == rhs.badgeNumber
}
}

struct CustomTabItemPreferenceKey: PreferenceKey {

typealias Value = [CustomTabItemPreferenceData]

static var defaultValue: [CustomTabItemPreferenceData] = []

static func reduce(value: inout [CustomTabItemPreferenceData], nextValue: () -> [CustomTabItemPreferenceData]) {
value.append(contentsOf: nextValue())
}
}

// TabItem
extension View {
func customTabItem<Content>(@ViewBuilder content: @escaping () -> Content) -> some View where Content: View {
self.preference(key: CustomTabItemPreferenceKey.self, value: [
CustomTabItemPreferenceData(tag: 0, item: AnyView(content()), stringDescribing: String(describing: content()), badgeNumber: 0)
])
}
}

// Tag
extension View {
func customTag(_ tag: Int, badgeNumber: Int = 0) -> some View {

self.transformPreference(CustomTabItemPreferenceKey.self) { (value: inout [CustomTabItemPreferenceData]) in

guard value.count > 0 else { return }
value[0].tag = tag
value[0].badgeNumber = badgeNumber

}
.transformPreference(CustomTabItemPreferenceKey.self) { (value: inout [CustomTabItemPreferenceData]) -> Void in

guard value.count > 0 else { return }
value[0].tag = tag
value[0].badgeNumber = badgeNumber
}
.tag(tag)
}
}

这是用法:

struct MainTabsView: View {
var body: some View {
// TabView
CustomTabView {
A()
.customTabItem { ... }
.customTag(0, badgeNumber: 1)
B()
.customTabItem { ... }
.customTag(2)
C()
.customTabItem { ... }
.customTag(3)
}
}
}

我希望这对你们有用,如果你知道更好的方法,请告诉我!

关于SwiftUI 将 TupleView 转换为 AnyView 数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59608417/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com