SwiftUI选项卡查看页面选项卡查看样式是否阻止更改选项卡



我在PageViewTabStyle的SwiftUI中有一个TabView,所以我可以从一页滑动到另一页。我想要一个";锁";当前视图已就位,因此用户无法滑动。在谷歌上搜索和阅读文档对我来说并没有什么明显的结果,所以我希望so上的专家能帮助我。

简而言之,我的代码看起来像

TabView {
ForEach(0..<5) { idx in
Text("Cell: (idx)")
}
}
.tabViewStyle(PageTabViewStyle())

我找到了disabled属性,但似乎整个视图中的所有点击事件都被忽略了——我只是想防止用户切换选项卡(或者,在这种特殊情况下,滑动或按下页面点来切换页面(。我已经尝试过将gesture属性设置为nil的解决方案,但这似乎并不能真正阻止滑动手势更改页面(不过indexDisplayMode位很好!(

非常感谢您的帮助!谢谢

上述参考文献中的解决方案有效,只是滑动不是被gesture(nil)阻止的,而是被gesture(DragGesture())阻止的。视图应该是完整的选项卡内容视图范围,如

TabView {
ForEach(0..<5) { idx in
Text("Cell: (idx)")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
.gesture(DragGesture())      // this blocks swipe
}
}
.tabViewStyle(PageTabViewStyle())

使用Xcode 12.1/iOS 14.1 进行测试

*,当然,它可以被设置为有条件的,如https://stackoverflow.com/a/63170431/12299030

要阻止TabView中的所有滑动手势,必须使用.simultaneousGesture(DragGesture())来阻止子视图中的所有移动手势以及

TabView {
ForEach(0..<5) { idx in
Text("Cell: (idx)")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
.simultaneousGesture(DragGesture())
}
}
.tabViewStyle(PageTabViewStyle())

对我有效的解决方案就是这个。它禁止通过滑动来更改选项卡,并保持屏幕上启用拖动手势,就像我在某些屏幕上使用List .onDelete一样。

它只能从iOS 16 中获得

@State private var selectedTab = 1
TabView(selection: $selectedTab) {
Text("Tab 1")
.tag(0)
.toolbar(.hidden, for: .tabBar)
Text("Tab 2")
.tag(1)
.toolbar(.hidden, for: .tabBar)
Text("Tab 3")
.tag(2)
.toolbar(.hidden, for: .tabBar)
}

我决定使用一些反射来推出自己的解决方案。它支持if语句(条件视图(和ForEach视图,并使用.tag()修饰符进行标识。

private enum PagingTransition {
case next, previous
var value: AnyTransition {
switch self {
case .next:
return AnyTransition.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading))
case .previous:
return AnyTransition.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing))
}
}
}
private func isOptional(_ instance: Any) -> Bool {
let mirror = Mirror(reflecting: instance)
let style = mirror.displayStyle
return style == .optional
}
/// Erases generics to get views out of `ForEach`
fileprivate protocol ViewGeneratable {
func generateViews() -> [any View]
}
extension ForEach: ViewGeneratable {
func generateViews() -> [any View] {
self.data.map { self.content($0) as! any View }
}
}
/// A paging `TabView` replacement that doesn't allow for the user to interact
/// Follows SwiftUI calling conventions as best as possible with dirty reflection
/// https://www.fivestars.blog/articles/inspecting-views/
struct RestrictedPagingView<SelectionType: Hashable & Comparable, Content: View>: View {
let selection: SelectionType
@State private var selectionInternal: SelectionType
@State private var transition: AnyTransition = PagingTransition.next.value
private var views: [SelectionType: any View] = [:]

init(selection: SelectionType, @ViewBuilder content: () -> Content) {
self.selection = selection
self._selectionInternal = State(initialValue: selection)

// Attempt reflection
generateViews(from: content(), withBaseTag: selection)
}

/// This is the most big brain shit I've coded in a long time
/// Reflects SwiftUI views and puts them in a dictionary to use within the paging view
private mutating func generateViews(from instance: Any, withBaseTag baseTag: SelectionType) {
let mirror = Mirror(reflecting: instance)

// Is this a tuple view?
if let value = mirror.descendant("value") {
// Yes, so call this function recusrively until it isn't
let count = Mirror(reflecting: value).children.count
for i in 0..<count {
generateViews(from: mirror.descendant("value", ".(i)")!, withBaseTag: baseTag)
}
} else if isOptional(instance) {
// This is an Optional, so check if it has a value
if let child = mirror.children.first?.value {
// It does, send it back through the function
generateViews(from: child, withBaseTag: baseTag)
}
} else if let content = mirror.descendant("content") {
// This is a ForEach loop, so unwrap and deal with all views separately
if mirror.descendant("contentID") != nil {
for view in (instance as! ViewGeneratable).generateViews() {
generateViews(from: view, withBaseTag: baseTag)
}
return
}
// This is a tagged view, extract the tag and the content and put them in the dictionary
let tag: SelectionType = mirror.descendant("modifier", "value", "tagged") as! SelectionType
views[tag] = (content as! any View)
} else {
// Just insert the view with a baseline tag
views[baseTag] = (instance as! any View)
}
}

// TODO: Handle removed conditional views more gracefully
var body: some View {
ForEach(views.keys.sorted(by: >), id: .self) { idx in
if idx == selectionInternal {
AnyView(views[idx]!)
.transition(transition)
}
}
.onChange(of: selection) { newSelection in
if newSelection > selectionInternal {
transition = PagingTransition.next.value
} else {
transition = PagingTransition.previous.value
}
withAnimation(.easeInOut(duration: 0.25)) {
selectionInternal = newSelection
}
}
}
}
private struct RestrictedPagingViewPreview: View {
@State private var index = 1
@State private var allow = false

var body: some View {
VStack {
RestrictedPagingView(selection: index) {
ZStack {
Rectangle().foregroundColor(.blue)
Text("Hi")
}.tag(1)
ZStack {
Rectangle().foregroundColor(.green)
Text("Second")
}.tag(2)
ZStack {
Rectangle().foregroundColor(.red)
Text("Third")
}.tag(3)
ZStack {
Rectangle().foregroundColor(.yellow)
Button("FOURTH") {
print("button activated")
}
}.tag(4)
if allow {
ZStack {
Rectangle().foregroundColor(.orange)
Text("Should be hidden (5)")
}.tag(5)
ZStack {
Rectangle().foregroundColor(.purple)
Text("Should be hidden (6)")
}.tag(6)
}
ForEach(7..<11, id: .self) { tagVal in
ZStack {
Rectangle().foregroundColor(.cyan)
Text("This view is generated by a ForEach loop! ((tagVal))")
}
.tag(tagVal)
}
}
.border(Color.green)
Button("INCR") {
index += 1
}
Button("INCR 2") {
index += 2
}
Button("DECR") {
index -= 1
}
Button("DECR 2") {
index -= 2
}
Toggle("Show", isOn: $allow)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.border(Color.red)
}
}
struct RestrictedPagingView_Previews: PreviewProvider {
static var previews: some View {
RestrictedPagingViewPreview()
}
}

最新更新