让 SwiftUI TabView 包装其内容



是否可以制作包装其内容的分页TabView?我不知道内容的高度(它是一个调整大小以适应屏幕宽度的图像),所以我不能使用frame修饰符。

我的代码如下所示:

ZStack(alignment: .topTrailing) {
TabView {
ForEach(data) { entry in
VStack {
entry.image
.resizable()
.scaledToFit()
.frame(maxWidth: .infinity)
Text(entry.description)
}
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
Color.red
.frame(width: 20, height: 20)
}

问题是TabView和屏幕一样大,PageIndicator被放置在屏幕的右上角而不是图像的右上角。坦克的任何建议。

编辑:

我添加了可重现的代码。页面指示器被红色矩形取代。

struct Test: View {
struct Entry: Identifiable {
let image: Image
let description: String
var id: String { description }
}
let data = [
Entry(image: Image(systemName: "scribble"), description: "image 1"),
Entry(image: Image(systemName: "trash"), description: "image 2")
]
var body: some View {
ZStack(alignment: .topTrailing) {
TabView {
ForEach(data) { entry in
VStack {
entry.image
.resizable()
.scaledToFit()
.frame(maxWidth: .infinity)
Text(entry.description)
}
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
Color.red
.frame(width: 20, height: 20)
}
}
}

您的PageIndicator没有放在图像上,因为您没有将其放置在那里。您将它放置在VStack顶部的图层中,该恰好包含文本和可能比屏幕短的图像。如果您希望图像上的PageIndicator,则需要专门执行此操作。您没有提供最小、可重现的示例,但执行了如下工作:

TabView {
ForEach(data) { entry in
VStack {
entry.image
.resizable()
.scaledToFit()
.overlay(
PageIndicator()
)
.frame(maxWidth: .infinity)
.overlay(
PageIndicator()
)
Text(entry.description)
}
}
}
.tabViewStyle(.page(indexDisplayMode: .never))

.overlay()需要放在.frame()之前,以便它停留在图像上,而不是覆盖.frame()

编辑:

根据MRE和注释,这是另一种解决方案,它将PageIndicator与图像顶部对齐,但不随图像滚动。请注意,这不是一个完美的 MRE,因为图像高度不同,但此解决方案实际上也考虑到了这一点。最后,我在图像上添加了黄色背景,以显示事物正确对齐。

struct Test: View {
struct Entry: Identifiable {
let image: Image
let description: String

var id: String { description }
}

let data = [
Entry(image: Image(systemName: "scribble"), description: "image 1"),
Entry(image: Image(systemName: "trash"), description: "image 2")
]

@State private var imageTop: CGFloat = 50

var body: some View {
ZStack(alignment: .topTrailing) {
TabView {
ForEach(data) { entry in
VStack {
entry.image
.resizable()
.scaledToFit()
.frame(maxWidth: .infinity)
.background(Color.yellow)
.background(
GeometryReader { imageProxy in
Color.clear.preference(
key: ImageTopInGlobal.self,
// This returns the top of the image relative to the TabView()
value: imageProxy.frame(in: .named("TabView")).minY)
}
)
Text(entry.description)
}
}
}
// This gives a reference to another container for comparison
.coordinateSpace(name: "TabView")
.tabViewStyle(.page(indexDisplayMode: .never))
VStack {
Spacer()
.frame(height: imageTop)
Color.red
.frame(width: 20, height: 20)
}
}
// You either have to ignore the safe area or account for it with regard to the TabView(). This was simpler.
.edgesIgnoringSafeArea(.top)
.onPreferenceChange(ImageTopInGlobal.self) {
imageTop = $0
}
}
}
private extension Test {
struct ImageTopInGlobal: PreferenceKey {
static let defaultValue: CGFloat = 0

static func reduce(value: inout CGFloat,
nextValue: () -> CGFloat) {
value = nextValue()
}
}
}

最终编辑:

针对最后一条评论,我的回答是有条件的否定。我认为没有任何方法可以制作一个TabView拥抱它的内容。(我把使用术语wrap的原始问题理解为拥抱,因为TabView总是"包装"其内容。如果尝试使用首选项键,TabView将折叠。必须有一个minHeightheight来防止这种情况破坏拥抱尝试的目的。

最新更新