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: metabblv@163.com")
            Text("City: Shanghai")
        }
        //通过Stack修改对齐方式
        GroupBox {
            VStack(alignment:.leading) {
                Text("Your account")
                    .font(.headline)
                Text("Username: metabblv@163.com")
                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问题。

调试结果

相关推荐
小溪彼岸2 天前
【iOS小组件】小组件尺寸及类型适配
swiftui·swift
文件夹__iOS7 天前
[SwiftUI 开发] @dynamicCallable 与 callAsFunction:将类型实例作为函数调用
ios·swiftui·swift
小溪彼岸7 天前
【iOS小组件】iOS17与低版本兼容适配
swiftui·swift
Mamong8 天前
SwiftUI疑难杂症(1):sheet content多次执行
ios·swiftui·swift
AUV110712 天前
Mac剪贴板历史全记录!
macos·swiftui·mac·效率工具·实用工具·剪贴板·clipboard
AUV110712 天前
Mac 上哪个剪切板增强工具比较好用? 好用剪切板工具推荐
macos·swiftui·mac·剪贴板·clipboard·剪贴板增强·app 推荐
多彩电脑14 天前
SwiftUI里的ForEach使用的注意事项
macos·ios·swiftui·swift
Swift社区16 天前
Apple 新品发布会亮点有哪些 | Swift 周报 issue 61
ios·swiftui·swift
humiaor17 天前
Xcode报错:No exact matches in reference to static method ‘buildExpression‘
swiftui·xcode
humiaor25 天前
Xcode报错:Return from initializer without initializing all stored properties
swiftui·binding