SwiftUI教程(三)常用View和Modifiers详细讲解和使用

SwiftUI教程系列文章汇总

本文主要讲述常见的View和Modifiers的认识和使用

主要内容:

  1. 常用View
  2. 常用Modifiers

1. 介绍

SwiftUI通过View视图搭建界面,使用Modifiers修饰器来修饰视图。系统提供了大量的视图和修饰器,并且还可以让我们自定义修饰器。
既可以手动写,也可以直接拖出到代码区或者预览区。这三种方式的结果都是一样的。

示意图:

view和modifiers

2、Text

显示一行或多行的只读文本视图,可以类似于OC中的label

//1、Text
Text("我是一个Text,**Markdown语法加粗了**").foregroundColor(.red)
  • 可以使用modifiers进行修饰视图。
  • 也可以使用Markdown语法进行修饰

3、Label

Label用户界面项的标准标签,包括图片和标题

//2、Label
VStack {
    //用户界面项的标准标签,由带有标题的图标组成。
    //labelStyle设置label样式
    Label("Lightning", systemImage: "bolt.fill")
    Label("Lightning", systemImage: "bolt.fill")
        .labelStyle(.iconOnly)
    Label("Lightning", systemImage: "bolt.fill")
        .labelStyle(.titleOnly)
    //自定义label样式
    Label("Lightning", systemImage: "bolt.fill")
//                        .labelStyle(.makeBody(RedBorderedLabelStyle))
    
    //多label统一样式
     VStack {
         Label("Rain", systemImage: "cloud.rain")
         Label("Snow", systemImage: "snow")
         Label("Sun", systemImage: "sun.max")
     }
     .labelStyle(.iconOnly)
    
    //组合标签
    Label {
        Text("wenyi")
            .font(.body)
            .foregroundColor(.primary)
        Text("ya")
            .font(.subheadline)
            .foregroundColor(.secondary)
    } icon: {
        Circle()
            .fill(.orange)
            .frame(width: 44, height: 44, alignment: .center)
            .overlay(Text("圆")).foregroundColor(.white)
    }
}

说明:

  1. 正常情况包含了图标和标题
  2. 也可以设置labelStyle,设置为只显示图标或标题
  3. labelStyle也可以直接设置到外部视图上,可以作用到内部所有的label上
  4. 我们还可以给一个label自由组合文本和图标。
  5. 上文中自定义一个Label,包含两个文本和一个图标
  6. 还可以自定义labelStyle

4、Button

//Button,文字和响应
Button {
    print("button点击响应")
} label: {
    Text("我是按钮")
}

说明:

  1. 按钮可以添加action进行响应,进行打印。
  2. 还有设置label,label上设置图标和Text。
  3. 这里仅设置了Text。

5、Link

通过提供目标URL和标题来创建链接

//Link
Link(destination: URL(string:"https://www.baidu.com/")!) {
    Text("Link")
}
    
Link("View Our Terms of Service",
      destination: URL(string: "https://www.example.com/TOS.html")!)
      
//设置OpenURLAction
Link("Visit Our Site", destination: URL(string: "https://www.example.com")!)
    .environment(\.openURL, OpenURLAction { url in
        print("Open \(url)")
        return .handled
                    })

说明:

  1. destination参数用来设置URL
  2. 还需要设置标题用来描述URL
  3. 通过设置OpenURLAction覆盖默认的URL打开的方式

6、Image

//直接拿Assets中图片
Image("wy")
    .resizable()
    .aspectRatio(contentMode: .fit)
//                UIImage.init(named: "wy.png")
            
            //加载网络图片
//            AsyncImage.

说明:

  1. 直接使用Image就可以拿到图片了,这里只能使用Assets中的图片
  2. 如果想要使用文件路径下的图片,需要使用UIImage
  3. 网络图片使用AsyncImage,注意要设置占位图片或占位文字,网络图片有可能会失败

7、TimelineView

根据时间表更新的视图

//定义
struct TimelineView<Schedule, Content> where Schedule : TimelineSchedule

//TimelineView
TimelineView(.periodic(from: Date.now, by: 1.0)) {
    context in
    Text(context.date.description).font(.title)
}

说明:

  1. 两个参数,一个是计划表,一个是执行的内容
  2. 在这里时间表是从当前时间,每1秒开始执行一次
  3. 执行的内容就是打印当前时间
  4. 因此知道个视图可以做定时器使用
  5. 可以传入三种参数
    1. .everyMinute 每分钟
    2. .periodic(from: , by: )从开始时间开始,多久开始更新
    3. .explicit(更新次数)

8、Canvas

绘制图形

//Canvas
Canvas { context, size in
    context.stroke(Path(ellipseIn: CGRect(origin: .zero, size: size)),with: .color(.blue), lineWidth: 3)
}.frame(width: 100, height: 50, alignment: .center).border(.red,width: 2)

说明:

  1. Canvas就是一个画布。我们可以对画布进行绘制丰富和动态的2D图形
  2. Canvas将GraphicsContext传递给用于执行即时模式绘图操作的闭包
  3. 通过闭包进行绘制,传入两个参数,一个是绘制的内容,一个是绘制的大小
  4. Canvas还传递一个CGSize值,您可以使用它来定制您绘制的内容

使用画布在SwiftUI视图中绘制丰富和动态的2D图形。画布将GraphicsContext传递给用于执行即时模式绘图操作的闭包。画布还传递一个CGSize值,您可以使用它来定制您绘制的内容。例如,你可以使用上下文的stroke(_:with:lineWidth:)命令来绘制一个Path实例:

9、TextEditor

显示可编辑文本界面的控件。相当于UITextView

//TextEditor
TextEditor(text: 
    .constant("Placeholder"))
    .frame(width: 100, height: 30, alignment: .center)

说明:

  1. constant可以设置默认值
  2. frame可以设置TextEditor的宽高,以及对齐方式。

10、TextField

文本输入框

//TextField,预览无法操作
TextField("首字母默认大写", text: $str).frame(width: 100, height: 56, alignment: .center)
    .textInputAutocapitalization(.never)
    //自动纠错
    .disableAutocorrection(true)
//                    .border(.red, width: 1)
//                    .cornerRadius(20)
    .overlay{
        RoundedRectangle(cornerRadius: 20)
            .stroke(.red, lineWidth: 10)
            .padding(-10)
    }
    .onSubmit {
        print("我点击了!")
    }
    
...
.contentShape(Rectangle())//追加热区设置
        .onTapGesture {
                print("tap")
                //热区
                UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
        }

说明:

  1. 可以添加.onSubmit来捕获提交事件
  2. 点击空白区域收起键盘
  3. 设置自动大小写的场景,比如首字母大写,全部大写,全不大写等等
  4. 自动纠错默认为false就是会自动纠错
  5. 直接设置.border设置圆角,只会设置外部的,不会设置内部的,因此需要手动绘制一个图像进行覆盖
  6. 注意“热区”,只要有内容的区域都属于热区(即使这里是空白的),热区在这里不属于空白区域
  7. 因此需要追加热区设置.contentShape(Rectangle())

11、ColorPicker

颜色选择器(UIKit没有)

@State var textColor = Color.red
//ColorPicker
//supportsOpacity是否设置透明度
ColorPicker("picker", selection: $textColor, supportsOpacity: false).font(.largeTitle).foregroundColor(textColor)

说明:

  1. 设置Picker的标题,还有设置一个选取器selection
  2. 这里拿到的$textColor就是不断刷新的颜色
  3. supportsOpacity是设置是否设置透明度,其实还可以设置Pciker的很多东西

12、Picker

选择器

@State var selectedCity = city.xian

//Picker
Picker(selection: $selectedCity, label: Text("Picker").frame(width: 50, height: 10, alignment: .center)) {
    Text("taiyuan").tag(city.taiyuan)
    Text("xian").tag(city.xian)
    Text("datong").tag(city.datong)
}.border(.orange)
Text("this city is : \(selectedCity.rawValue)")

说明:

  1. $selectedCity就是获取的值
  2. 设置多种选择项

13、toolbar

//设置工具栏
NavigationView {
    Text("Hello World!").navigationTitle("navigation")
        .toolbar {
            ToolbarItem(placement: .navigationBarLeading) {
                Button("Edit") {}
            }
            ToolbarItem(placement: .navigationBarTrailing) {
                Button("back") {}
            }
        }
}

说明:

  1. 设置TollbarItem项是每个按钮
  2. 有很多样式可以选择
  3. 不仅可以用于导航栏,可以用在任意的工具栏中

14、ProgressView

进度条视图

//ProgressView
//                ProgressView(value: /*@START_MENU_TOKEN@*/0.5/*@END_MENU_TOKEN@*/)
ProgressView(value: progress, total: 10, label: {
    Text("WY")
}, currentValueLabel: {
    Text("start")
})
.progressViewStyle(.circular)
Button("加1") {
    progress += 1
}
ProgressView().progressViewStyle(.linear)

}

说明:

  1. 进度条颜色的设置.tint(.red)
  2. view的颜色的设置.background(.green)
  3. 进度条值的设置,默认是0-1,我们可以也可以设置总数
  4. 空进度条的样式,可以直接设置成ProgressView(),此时是加载图,如果需要设置空进度条,就.progressViewStyle(.linear)
  5. .progressViewStyle()就可以设置进度条的样式

15、Slider

滑动块

@State private var isEditing = false
Slider(
        value: $speed,
        in: 0...100,
        step: 5
    ) {
        Text("Speed")
    } minimumValueLabel: {
        Text("0")
    } maximumValueLabel: {
        Text("100")
    } onEditingChanged: { editing in
        isEditing = editing
    }
Text("\(speed)")
        .foregroundColor(isEditing ? .red : .blue)

说明:

  1. 滑动时动态获取到的值是value
  2. in是设置滑动范围
  3. step是可以设置整个范围多少等分(可以设置自己的精确度)
  4. onEidtingChanged是滑动时的响应操作
  5. 还可以设置最大范围和最小范围显示

16、Togle

切换按钮,相当于UISwitch

Toggle(isOn: $vibrateOnRing) {
    Text("Vibrate on Ring")
}
Toggle("Vibrate on Ring", isOn: $vibrateOnRing)
  • Toggle就是切换按钮
  • 这两种写法都可以,很简单,最后拿到vibrateOnRing就可以

17、Stepper

当您希望用户在增加或减少值时进行粒度控制时,请使用Stepper控件

//Stepper
Stepper {
    //左侧文本
        Text("Value: \(value) Color: \(colors[value].description)")
    } onIncrement: {//加操作
        incrementStep()
    } onDecrement: {//减操作
        decrementStep()
    }
    .padding(5)
    .background(colors[value])//设置颜色
//设置范围,且默认加减
Stepper(value: $value,
                in: range,
                step: step) {
            Text("Current: \(value) in \(range.description) " +
                 "stepping by \(step)")
        }
            .padding(10)
  • 可以给Stepper设置点击的效果:
  • 默认效果就是进行数值的加减计算,此时需要设置数值的总数和步长
  • 如果自定义效果,就需要设置onIncrement和onDecrement来自定义响应

18、Gradient

图形渐变

代码:

//图形渐变
//角渐变
AngularGradient(gradient: Gradient(colors: [Color.red, Color.blue,.purple,.red]), center: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
    
//椭圆
EllipticalGradient(colors:/*@START_MENU_TOKEN@*/[Color.blue, Color.green]/*@END_MENU_TOKEN@*/, center: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, startRadiusFraction: /*@START_MENU_TOKEN@*/0.0/*@END_MENU_TOKEN@*/, endRadiusFraction: /*@START_MENU_TOKEN@*/0.5/*@END_MENU_TOKEN@*/)

//线性
LinearGradient(gradient: /*@START_MENU_TOKEN@*/Gradient(colors: [Color.red, Color.blue])/*@END_MENU_TOKEN@*/, startPoint: /*@START_MENU_TOKEN@*/.leading/*@END_MENU_TOKEN@*/, endPoint: /*@START_MENU_TOKEN@*/.trailing/*@END_MENU_TOKEN@*/)
    
//辐射渐变
RadialGradient(gradient: /*@START_MENU_TOKEN@*/Gradient(colors: [Color.red, Color.blue])/*@END_MENU_TOKEN@*/, center: .bottomLeading, startRadius: 100, endRadius: 120)
图形渐变

19、searchable


//创建一个model项
struct ItemModel: Identifiable {
    var id = UUID()
    var name: String
    var detailView: DetailView
}

//创建一个详情View
struct DetailView: View, Identifiable {
    var id = UUID()
    var detail: String
    @State var text = ""
    var body: some View {
        VStack (alignment: .leading){
            Text(detail).font(.largeTitle).foregroundColor(.gray).bold()
                .searchable(text: $text){
                    Text("大同").searchCompletion("大同")
                    Text("太原").searchCompletion("太原")
                    Text("太原").searchCompletion("太原")
                    Text("太原").searchCompletion("太原")
                    Text("太原").searchCompletion("太原")
                }
            Spacer()
        }
       
    }
}

//定义一个数组
let datas: [ItemModel] = [
    ItemModel(name: "太原", detailView: DetailView(detail: "山西省会")),
    ItemModel(name: "西安", detailView: DetailView(detail: "陕西省会")),
    ItemModel(name: "银川", detailView: DetailView(detail: "宁夏省会")),
    ItemModel(name: "西宁", detailView: DetailView(detail: "青海省会")),
    ItemModel(name: "呼和浩特", detailView: DetailView(detail: "内蒙省会")),
    ItemModel(name: "郑州", detailView: DetailView(detail: "河南省会"))
]

//创建一个viewModel,提供了数组项
//并且还有一个filtedItems用来过滤每一项
class ViewModel: ObservableObject {
    
    @Published var allItems: [ItemModel] = datas
    @Published var searchedItem: String = ""
    
    var filtedItems: [ItemModel] {
        searchedItem.isEmpty ? allItems : allItems.filter({ str in
            str.name.lowercased().contains(searchedItem.lowercased())
        })
    }
}

struct ContentView: View {
    @ObservedObject var vm = ViewModel()
    var body: some View {
        NavigationView {
            List {
                ForEach(vm.filtedItems) {item in
                    NavigationLink(item.name, destination:  item.detailView)
                }
            }
            .navigationTitle(Text("搜索页面"))
            .searchable(text: $vm.searchedItem, prompt: "输入您想要搜索的省会名称")
        }
    }
}

说明:

  • 如果给List添加.searchable,必须配合NavigationView
  • 给Text添加搜索框,可以给搜索框添加可选值,还可以给这些值设置响应

20、List

列表,就是OC中的TableView,是对UITableView的包装

List{
    ForEach(todos, id:\.name){ (todo) in
        Text("wenyi")
    }
}

说明:

  1. List里面设置每一项cell
  2. 通过ForEach进行循环设置。
  3. 每个cell显示一个文本"wenyi".

21、TabView

Tab的View,就是之前的tabbar

//TabView
TabView() {
    Text("Tab Content 1").tabItem {
        Image(systemName: "person")
        Text("Tab Label 1")
    }.tag(1).badge(Text("news"))
    
    Text("Tab Content 2").tabItem {
        Text("Tab Label 2")
    }.tag(2)
}

说明:

  1. 这里设置了两项,每项包括自己的文本内容,以及item自身的内容
  2. tabItem可以设置图片和文字。
  3. 还可以设置标记.badge
TabView

设置动画效果:

TabView() {
    Text("Tab Content 1").tabItem {
        Image(systemName: "person")
        Text("Tab Label 1")
    }.tag(1).badge(Text("news"))
    
    Text("Tab Content 2").tabItem {
        Text("Tab Label 2")
    }.tag(2)
}.tabViewStyle(.page).background(.orange)

说明:

  1. item只有两个点
  2. 此时也可以取消白点(indexDispalyMode: .never)
  3. 这里的点如果想要设置图片,也是可以的

22、OnOpenURL

用来跳转页面,通过其他APP进行跳转到当前页面

//OnOpenURL
struct ContentView: View {
    @State var show = true
    @State var tabSelection = 1
    var body: some View {
        TabView(selection: $tabSelection) {
            Text("Tab Content 1").tabItem {
                Image(systemName: "person")
                Text("Tab Label 1")
            }.tag(1).badge(Text("news"))
            
            Text("Tab Content 2").tabItem {
                Image(systemName: "person")
                Text("Tab Label 2")
            }.tag(2)
        }.onOpenURL { url in
            switch url.host {
            case "tab1":
                tabSelection = 1
            case "tab2":
                tabSelection = 2
            default:
                show.toggle()
            }
        }
        .sheet(isPresented: $show) {
            Text("URL参数错误")
        }
    }
}

设置scheme

设置scheme

结果:

结果

说明:

  1. 给TabView增加一个跳转连接,可以从外部应用跳转到当前页面
  2. 需要在info中设置一下scheme
  3. 如果想要删除URL type中的项,可以在Info.plist中删除,而且重新打开XCode
  4. .sheet用来弹出文本视图的弹框进行参数错误提醒

23、interactiveDismissDisab

禁止手势滑动关闭界面,必须加在按钮的后面

Button("Open Sheet") {
    show.toggle()
}
.sheet(isPresented: $show) {
    Button("Close") {
        show.toggle()
    }
    .interactiveDismissDisabled()
}

说明:

  • 给按钮设置禁止手势滑动关闭界面。此时就只能通过返回按钮返回。不能左滑返回了。

24、contextMenu

文本菜单

struct ContentView: View {
    @State var backgroundColor = Color.red
    @State var isShow = true
    var body: some View {
        Text("Hello world~").bold().font(.largeTitle).foregroundColor(.white).background(backgroundColor)
            .contextMenu (isShow ? ContextMenu{
                Button("Red") {
                    backgroundColor = .red
                }
                Button("Green") {
                    backgroundColor = .green
                }
                Button("Blue") {
                    backgroundColor = .blue
                }
                Button {
                    backgroundColor = .yellow
                } label: {
                    Label("yellow", systemImage: "scribble")
                }
            } : nil )
    }
}

说明:

  1. 给一个view增加菜单选项
  2. 也可以设置是否显示菜单
  3. 案例中菜单选项有多个按钮,点击按钮后可以改变背景颜色。
  4. 背景颜色设置到文本视图中,所以通过点击菜单选项就可以改变文本视图的背景颜色。

25、Menu

菜单视图,可以嵌套

Menu("Menu") {
    Text("Item1")
    Text("Item2")
    Text("Item3")
    Button("Red") {
        backgroundColor = .red
    }
    Button("Green") {
        backgroundColor = .green
    }
    Button("Blue"){
        backgroundColor = .blue
    }
    Button("Yellow"){
        backgroundColor = .yellow
    }
    Menu("Menu") {
        Text("Item1")
        Text("Item2")
        Text("Item3")
        Button("Red") {
            backgroundColor = .red
        }
        Menu("Menu") {
            Text("Item1")
            Text("Item2")
            Text("Item3")
            Button("Red") {
                backgroundColor = .red
            }
        }
    }
}

说明:

  1. Menu来实现菜单视图,可以设置标题
  2. 菜单中可以放文本视图和按钮视图
  3. 并还可以再嵌套菜单视图,实现多层级菜单的选择。

26、Form

表单视图

Form和List在使用和现象是没有区别的,底层也是一样的,都是对UItableView的封装,可以看做是分组的List
用于分组数据输入的控件的容器,如在设置或检查器中。

Form {
    Text("item1")
    Text("item2")
    Text("item3")
}

说明:

  • 案例中显示三个视图文本

27、ScrollView

ScrollViewReader { proxy in
    Button("gotoBottom"){
        proxy.scrollTo(90)
    }
    ScrollView(.vertical, showsIndicators: false) {
        VStack(alignment: .center, spacing: 10) {
            ForEach(0..<100) {
                Text("cell \($0)").font(.title)
            }
            .frame(maxWidth:.infinity)
        }
    }
}

说明:

  1. 设置方向,可以选择垂直还是水平方向滑动
  2. 设置是否显示Indicators
  3. 通过代理增加按钮跳转某个cell,此时需要使用ScrollViewReader来实现。
  4. 比如这里点击按钮后,可以直接跳转到底90个cell。

28、Alert

//Alert
Button("show Alert") {
    show.toggle()
}
.alert(isPresented: $show) {
    Alert.init(title: Text("title"), message: Text("message"), dismissButton: .cancel())
}
    
Button("show Dialog") {
    show.toggle()
}.confirmationDialog("dialog", isPresented: $show) {
    Button("btn1") {}
    Button("btn2") {}
    Button("btn3") {}
}
}

说明:

  1. 这里可以设置多种按钮类型
  2. 可以设置点击事件的响应
  3. 还可以设置按钮列表,进行选择点击
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容