SwiftUI基础篇Container

概述

文章主要分享SwiftUI Modifier的学习过程,将使用案例的方式进行说明。内容浅显易懂,Container展示部分调试结果,不过测试代码是齐全的。如果想要运行结果,可以移步Github下载code -> github案例链接

1、Container概述

SwiftUI的设计源自于便捷、易用,可以根据需求将一个视图放置在另一个视图中。这在处理常用的主要容器视图时尤其有用,比如导航控制器和tabBar控制器。可以把任何视图放到另一个容器视图中,SwiftUI会自动调整它的布局。

在这方面,SwiftUI自己的容器-NavigationStackTabViewGroup等--与用自己的视图组合制作的容器没有什么不同。

2、使用TabView在tabbar中嵌入视图

SwiftUI的TabView提供了一个等价于UITabBarController的控件,允许我们让用户使用屏幕底部的一个栏在几个活跃视图之间切换。在其基本形式中,应该为每个item提供一个图像和标题,如果希望以编程方式控制哪个item是active的,还可以添加一个标记。

2.1、基本样式

创建两个具有不同图像、表土和标签的视图。

Swift 复制代码
struct FFTabViewEnbedViews: View {
    var body: some View {
        TabView(selection: $selectedView) {
            Text("First View")
                .padding()
                .tabItem {
                    Image(systemName: "1.circle")
                    Text("First")
                }
                .tag(1)
            Text("Second View")
                .padding()
                .tabItem {
                    Image(systemName: "2.circle")
                    Text("Second")
                }
                .tag(2)
        }
    }

2.2、通过Label创建tabItem

除了分指定文本和图像,还可以使用Label视图将它们组合在一起。

Swift 复制代码
struct FFTabViewEnbedViews: View {
    var body: some View {
        TabView(selection: $selectedView) {
            Text("Third View")
                .padding()
                .tabItem {
                    Label("Third", systemImage: "3.circle")
                }
                .tag(3)
        }
    }

从iOS15开始,不用明确的使用SF Symbols图标的填充变化,因为系统会自动填充。如果添加label,则可以通过修改item视图的选择以编程方式控制active的item。

2.3、通过tag切换Item

把每个item的content中添加一个Button来模拟点击,通过添加一些新的状态来跟踪哪个item是活跃的,然后将其附加到TabView的tag。

Swift 复制代码
struct FFTabViewEnbedViews: View {
    @State var selectedView = 4
    var body: some View {
        TabView(selection: $selectedView) {
            Button("Show Five View") {
                selectedView = 5
            }
            .padding()
            .tabItem {
                Label("Third", systemImage: "4.circle")
            }
            .tag(4)
            
            Button("Show Four View") {
                selectedView = 4
            }
            .padding()
            .tabItem {
                Label("Third", systemImage: "5.circle")
            }
            .tag(5)
        }
    }

只要数据类型符合Hashable,item的label可以是任何类型。整数可能是最好的,但是如果要进行任何有意义的程序化导航,则应该确保将标记放在视图的中心位置,例如静态属性。这是可以在许多地方共享的方式,从而降低出错的风险。

调试结果

3、使用TabView创建UIPageViewController

SwiftUI的TabView可以当作为一个UIPageViewController,可以制作在多个屏幕间滑动的Content,底部的分页点显示具体位置。要激活页面的视图样式,将.tabViewStyle()修饰符附加到TabView,传入参数.page。

3.1、.tabViewStyle(.page)

分页点是白色和半透明的白色,如果视图背景为白色,则看不到分页点

Swift 复制代码
struct FFTabViewScrollingPages: View {
    var body: some View {
        TabView {
            Text("First")
            Text("Second")
        }
        .tabViewStyle(.page)
    }
}

3.2、.indexViewStyle(.page(backgroundDisplayMode: .always))

为了解决这个问题,可以在tabViewStyle()设置额外的修饰符来要求SwiftUI放置背景

Swift 复制代码
struct FFTabViewScrollingPages: View {
    var body: some View {
        TabView {
            Text("First")
            Text("Second")
        }
        .tabViewStyle(.page)
        .indexViewStyle(.page(backgroundDisplayMode: .always))
    }
}

3.3、.tabViewStyle(.page(indexDisplayMode: .never))

可以通过像.page方法添加一个额外的参数来控制分页点的显示,如果不想显示分页点,可以直接传入.never

Swift 复制代码
struct FFTabViewScrollingPages: View {
    var body: some View {
        TabView {
            Text("First")
            Text("Second")
        }
        .tabViewStyle(.page(indexDisplayMode: .never))
    }
}

调试结果

4、使用Group对视图分组

如果需要几个视图作为一个整体--例如,一起转换,那么可以使用SwiftUI的Group视图,这一点尤其重要,因为由于潜在技术原因,一次最多向父视图添加10个视图

Swift 复制代码
struct FFGroupView: View {
    var body: some View {
        //包含10个Text的Stack,当添加了11个视图的时候就会报错(Extra argument in call)
        //得到的错误是对成员"buildBlock()"的模糊引用。
        VStack {
            Text("Line 1")
            Text("Line 2")
            Text("Line 3")
            Text("Line 4")
            Text("Line 5")
            Text("Line 6")
            Text("Line 7")
            Text("Line 8")
            Text("Line 9")
            Text("Line 0")
        }
        Divider()
        //这是因为SwiftUI的视图构建系统有各种各样的代码,可以添加1-10个视图,
        //但不能添加第11个视图,但是,可以使用Group
        VStack {
            Group {
                Text("Line 1")
                Text("Line 2")
                Text("Line 3")
                Text("Line 4")
                Text("Line 5")
                Text("Line 6")
            }
            
            Group {
                Text("Line 7")
                Text("Line 8")
                Text("Line 9")
                Text("Line 0")
                Text("Line 11")
            }
        }
        //这种情况下就巧妙的绕过了10个视图的限制,因为这个时候VStack只包含了两个Group
    }
}

调试结果

5、StatusBar的显示和隐藏

使用SwiftUI的StatusBar()修饰符隐藏和显示iOS状态栏。接受一个隐藏参数,该参数必须为true或false,取决于需求。

Swift 复制代码
struct FFStatusbarHideAndShow: View {
    @State private var hideStatusBar = false
    
    var body: some View {
        //此修饰符仅在iOS上可用
        Text("No status bar, Please")
            .statusBar(hidden: false)
        //如果希望状态栏可见性依赖于某些程序状态,使用@state来代替hard-coded。
        //例如,创建一个hideStatusBar的bool值,当按钮被点击时,该bool值会被切换,
        //从而控制状态栏是否显示。
        Button("Toggle Status Bar") {
            withAnimation {
                hideStatusBar.toggle()
            }
        }
        .statusBar(hidden: hideStatusBar)
    }
}

此case由于是设置了两遍,Text与Button同时设定了statusBar,想要看到Button点击的动态效果,将Text的.statusBar(hidden: false)代码注释掉,有干扰。

调试结果

6、使用DisclosureGroup隐藏和显示内容

SwiftUI有一个专用的DisclosureGroup视图,它显示一个披露指示器,并包含其中的内容。在最简单的形式中,他可以完全由用户控制,但也可以将其绑定到一个bool属性,确定当前内容是否可见。

6.1、DisclosureGroup

创建一个包含大量文本的DisclosureGroup

Swift 复制代码
struct FFDisclosureGroupHide: View {
    var body: some View {
        DisclosureGroup("Show Terms") {
            Text("噫吁嚱,危乎高哉!蜀道之难,难于上青天!蚕丛及鱼凫,开国何茫然!尔来四万八千岁,不与秦塞通人烟。西当太白有鸟道,可以横绝峨眉巅。地崩山摧壮士死,然后天梯石栈相钩连。上有六龙回日之高标,下有冲波逆折之回川。黄鹤之飞尚不得过,猿猱欲度愁攀援。青泥何盘盘,百步九折萦岩峦。扪参历井仰胁息,以手抚膺坐长叹。")
        }
        .frame(width: 300)
        .padding()
    }
}

6.2、通过修改bool值,控制是否展开Content

如果想要跟踪披露组是否打开,可以将其绑定到bool值

Swift 复制代码
struct FFDisclosureGroupHide: View {
    @State private var revealDetails = true
    
    var body: some View {
        DisclosureGroup("Show Terms", isExpanded: $revealDetails) {
            Text("噫吁嚱,危乎高哉!蜀道之难,难于上青天!蚕丛及鱼凫,开国何茫然!尔来四万八千岁,不与秦塞通人烟。西当太白有鸟道,可以横绝峨眉巅。地崩山摧壮士死,然后天梯石栈相钩连。上有六龙回日之高标,下有冲波逆折之回川。黄鹤之飞尚不得过,猿猱欲度愁攀援。青泥何盘盘,百步九折萦岩峦。扪参历井仰胁息,以手抚膺坐长叹。")
        }
        .frame(width: 300)
    }
}

调试结果

7、创建Toolbar并向其添加按钮

SwiftUI的toolbar()修饰符可以在顶部或底部任何地方方式按钮item,但只有视图嵌入到NavigationStack中时才可以。

7.1、ToolbarItem

如果你想把按钮放在屏幕底部的工具栏中,使用Toolbar(),然后创建一个Toolbar item,指定位置为.bottomBar

Swift 复制代码
struct FFToolbarButtons: View {
    var body: some View {
        NavigationStack {
            Text("Hello, World!")
                .padding()
                .navigationTitle("SwiftUI")
                .toolbar(content: {
                    ToolbarItem(placement: .bottomBar) {
                        Button("Press Me") {
                            print("Toolbar Bottom")
                        }
                    }
                })
        } 
    }
}

7.2、ToolbarItemGroup

要创建多个Button,使用ToolbarItemGroup而不是ToolbarItem,设置多个Button

Swift 复制代码
struct FFToolbarButtons: View {
    var body: some View {
        NavigationStack {
            Text("Hello, World!")
                .padding()
                .navigationTitle("SwiftUI")
                .toolbar(content: {
                    ToolbarItemGroup(placement: .bottomBar) {
                        Button("First") {
                            print("First")
                        }
                        Button("Second") {
                            print("Second")
                        }
                    }
                })
        }
    }
}

7.3、ToolbarItemGroup添加Spacer

如果想在ToolbarItemGroup中间添加间隔,添加Spacer()即可

Swift 复制代码
struct FFToolbarButtons: View {
    var body: some View {
        NavigationStack {
            Text("Hello, World!")
                .padding()
                .navigationTitle("SwiftUI")
                .toolbar(content: {

                    ToolbarItemGroup(placement: .bottomBar) {
                        Button("Third") {
                            print("T")
                        }
                        Spacer()
                        Button("Fouth") {
                            print("Fouth")
                        }
                    }
                })
        }
    }
}

调试结果

8、自定义ToolbarButton

SwiftUI的工具栏允许用户自定义item,5个步骤:

  1. 为toolbar提供唯一的、稳定的标识符字符串。
  2. 为每个可自定义toolbar的item提供唯一的、稳定的标识符字符串。
  3. 在.secondaryaction类别中放置可定制的button
  4. 为toolbar启用编辑器模式,以便所有的辅助操作都成为toolbar按钮
  5. "唯一、稳定"的标识符很重要,因为这是SwiftUI用来记住用户设置的--toolbar
Swift 复制代码
struct FFToolbarCustomize: View {
    var body: some View {
        NavigationStack {
            Text("SwiftUI")
                .navigationTitle("Welcome")
                .toolbar(id: "options") {
                    ToolbarItem(id: "settings", placement: .primaryAction) {
                        Button("Settings") {
                            print("Settings")
                        }
                    }
                    
                    ToolbarItem(id: "help", placement: .secondaryAction) {
                        Button("Help") {
                            print("Help")
                        }
                    }
                    
                    ToolbarItem(id: "email", placement: .secondaryAction) {
                        Button("Email Me") {
                            print("Email me")
                        }
                    }
                    
                    ToolbarItem(id: "credits", placement: .secondaryAction, showsByDefault: false) {
                        Button("Credits") {
                            print("Credits")
                        }
                    }
                    //默认情况下,这将使所有的次要操作button都可以单独自定义,
                    //但是如果你在ControlGroup中包装两个或更多Button,
                    //则它们将被附加用于自定义目的--必须同时添加或不添加
                    ToolbarItem(id: "font", placement: .secondaryAction) {
                        ControlGroup {
                            Button {
                                print("ControlGroup - 1")
                            } label: {
                                Label("Decrease font size", systemImage: "textformat.size.smaller")
                            }
                            Button {
                                print("ControlGroup - 2")
                            } label: {
                                Label("Increase font size", systemImage: "textformat.size.larger")
                            }
                        } label: {
                            Label("Font size", systemImage: "textformat.size")
                        }
                    }
                }
                .toolbarRole(.editor)
        }
        //如果没有为ControlGroup添加Label,SwiftUI将为他包含的按钮使用Label
    }
}

调试结果

9、TabView或List-cell添加一个badge

SwiftUI的Badge()修饰符可以向TabView item视图和list-cell添加数字和文本,目的将用户的注意力吸引到一些弥补状态信息上--例如,TabView上的数字标识未读消息。

Swift 复制代码
struct FFTabViewBadge: View {
    var body: some View {
        TabView {
            VStack {
                List {
                    Text("Wi-Fi")
                        .badge("Lan solo")
                    Text("Bluetooth")
                        .badge("On")
                }
            }
            .tabItem {
                Label("Home", systemImage: "house")
            }
            .badge(5)
        }
    }
}

badge同样适用于List-cell,并自动以第二种颜色显示为右对齐文本。

调试结果

10、GroupBox可视化的对视图进行分组

SwiftUI的GroupBox视图将视图组合在一起,并在他后面放置浅色背景,以便突出。如果需要的话,还可以选择包含一个标题来制作组标题。

Swift 复制代码
struct FFGroupBox: View {
    var body: some View {
        GroupBox {
            Text("Your account")
                .font(.headline)
            Text("Username: [email protected]")
            Text("City: Shanghai")
        }
        //通过Stack修改对齐方式
        GroupBox {
            VStack(alignment:.leading) {
                Text("Your account")
                    .font(.headline)
                Text("Username: [email protected]")
                Text("City: Shanghai")
            }
        }
        //GroupBox的真正强大的功能,如果嵌套它们,会自动调整颜色,以便清晰的区分。
        GroupBox {
            Text("Outer Content")
            GroupBox {
                Text("Middle Content")
                GroupBox {
                    Text("Inner Content")
                }
            }
        }
    }
}

调试结果

11、如何隐藏Tabbar、navigationBar、toolbars

SwiftUI的toolbar()修饰符允许我们在需要的时候隐藏或显示任何系统栏,当你有一个TabView想要在导航推送之后隐藏的时候,这个修饰符特别有用。将修饰符附加到可以触发隐藏或显示toolbar的任何视图上。

11.1、

Swift 复制代码
struct FFBarsHidden: View {
    var body: some View {
        TabView {
            NavigationStack {
                NavigationLink("Tap Me") {
                    Text("Detail View")
                        .toolbar(.hidden, for: .tabBar)
                }
                .navigationTitle("Primary View")
            }
            .tabItem {
                Label("Home", systemImage: "house")
            }
        }
    }
}

如果没有指定要隐藏的是谁(for:tabbar),那么隐藏效果将流向最近的容器,可能是导航栏被隐藏。

11.2、动态隐藏NavigationBar

当随时改变传递给toolbar的值,就可以动态切换导航栏了

Swift 复制代码
struct FFBarsDetailView: View {
    @State private var showNavigationBar = true
    
    var body: some View {
        Text("Detail View")
            .toolbar(showNavigationBar ? .visible : .hidden)
            .onTapGesture {
                withAnimation {
                    showNavigationBar.toggle()
                }
            }
    }
}

struct FFBarsHidden: View {
    var body: some View {
        TabView {
            NavigationStack {
                NavigationLink("DetailView", destination: FFBarsDetailView.init())
                    .navigationTitle("Primary View")
            }
            .tabItem {
                Label("Detail", systemImage: "1.circle")
            }
        }
    }
}

调试结果

12、自定义Navigationbar、tabbar、toolbars的背景色

SwiftUI的toolbarBackground()修饰符可以自定义工具来在app中的外观,根据需求控制NavigationStack、TabView和其他toolbar样式。

12.1、设置Navigationbar背景色

显示包含100行的list,导航栏背景为蓝绿色

Swift 复制代码
struct FFBarsBackground: View {
    var body: some View {
        TabView {
            NavigationStack {
                List(0..<100) {
                    Text("Row \($0)")
                }
                .navigationTitle("100 Rows")
                .toolbarBackground(.teal, for: .navigationBar)
            }
            .tabItem {
                Label("First", systemImage: "1.circle")
            }
        }
    }
}

在这里选择的背景值在系统认为必要时使用,而不是总是可见的。因此,在上面的代码中,navigationbar开始是没有颜色的,滚动后会改变。

12.2、Toolbar单独着色

如果希望为一个或两个栏类型着色,或者希望为每个栏提供不同的样式,则可以向toolbarBackground提供第二个参数以获得外的控制。

Swift 复制代码
struct FFBarsBackground: View {
    var body: some View {
        //显示包含100行的list,导航栏背景为蓝绿色
        TabView {
            NavigationStack {
                List(0..<100) {
                    Text("Row \($0)")
                }
                .navigationTitle("100 Rows")
                .toolbarBackground(.orange, for: .navigationBar, .tabBar)
            }
            .tabItem {
                Label("Second", systemImage: "list.bullet")
            }
        }
    }

12.3、完全隐藏

可以完全隐藏背景,而不是指定背景颜色

Swift 复制代码
struct FFBarsBackground: View {
    var body: some View {
        //显示包含100行的list,导航栏背景为蓝绿色
        TabView {
            NavigationStack {
                List(0..<100) {
                    Text("Row-Third \($0)")
                }
                .navigationTitle("100 Rows")
                .toolbarBackground(.hidden)
            }
            .tabItem {
                Label("Third", systemImage: "list.bullet")
            }
        }
    }

当滚动时,列表内容直接出现在导航标题上。如果采用这种方法,请确保你的主要内容与toolbar重叠时不会发生UI问题。

调试结果

相关推荐
MaoJiu11 小时前
Flutter造轮子系列:flutter_permission_kit
flutter·swiftui
iOS阿玮17 小时前
社交的本质是价值交换,请不要浪费别人的时间。
uni-app·app·apple
大熊猫侯佩18 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(三)
数据库·swiftui·swift
大熊猫侯佩18 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(二)
数据库·swiftui·swift
大熊猫侯佩18 小时前
用异步序列优雅的监听 SwiftData 2.0 中历史追踪记录(History Trace)的变化
数据库·swiftui·swift
大熊猫侯佩18 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(一)
数据库·swiftui·swift
iOS阿玮2 天前
苹果2024透明报告看似更加严格的背后是利好!
uni-app·app·apple
大熊猫侯佩2 天前
SwiftUI 中如何花样玩转 SF Symbols 符号动画和过渡特效
swiftui·swift·apple
大熊猫侯佩2 天前
SwiftData 共享数据库在 App 中的改变无法被 Widgets 感知的原因和解决
swiftui·swift·apple
大熊猫侯佩2 天前
使用令牌(Token)进一步优化 SwiftData 2.0 中历史记录追踪(History Trace)的使用
数据库·swift·apple