美文网首页iOS技术资料
SwiftUI学习整理(一)

SwiftUI学习整理(一)

作者: 微笑_d797 | 来源:发表于2020-10-17 17:42 被阅读0次

最前面想说的话

转眼已经是2020年10月了,2020已经过去大半了,上半年的计划已经完成,所以下半年的学习应该提上日程了,利用两周时间每周一三学习周六整理归纳学习的东西发表一篇文章,二四锻炼,周五周日休息

SwiftUI 学习的想法

其实本来不是不打算先学习SwiftUI的,但是因为现在iOS14推出了小组件功能必须要用SwiftUI实现,所以提前学习一下是很有必要的。预计发表三篇关于SwiftUI的日志,分别整理SwiftUI的基础布局SwiftUI里的响应式布局,最后结合一个实际项目输出自己的心得
这篇文章参考的书籍是《SwiftUI和Combine编程》这本书第一二章,仅为自己学习归纳使用。

你好,SwiftUI

创建第一个项目

“选择 iOS tab 下的 Single View App 模板,Xcode 将为我们创建一个单页的 iOS app。”“接下来,将项目命名为 Calculator,并且选择 “Use SwiftUI” 作为用户界面的构建方式,让 Xcode 使用 SwiftUI 来创建第一个画面。“点击 Next 并选择合适的位置后,我们可以得到一个使用 SwiftUI 作为界面开发方式的新项目。在项目导航中找到 ContentView.swift,它的内容如下:

import SwiftUI
struct ContentView : View {
       var body: some View {
           Text("Hello World")
       }
}
struct ContentView_Previews : PreviewProvider {
        static var previews: some View {
              ContentView()
        }
}

上面代码中 ContentView 所定义的是实际的 UI,ContentView_Previews 则是一个满足了 PreviewProvider 的 dummy 界面。如果你的操作系统是 macOS 10.15 或以上的话,你可以使用编辑器右上角的切换编辑器视图按钮,选中 “Editor and Canvas” (快捷键 Option + Command + 回车),并单击右上角的 Resume 按钮。Xcode 预览界面将会读取 ContentView_Previews 的内容,并渲染在编辑器右侧的实时预览栏中。它就是 ContentView_Previews 的 previews 属性所返回的 View,也就是 ContentView 的内容。默认情况下,Xcode 会使用你当前选取的 iOS 模拟器作为预览尺寸,为了能让你得到的内容和书中一致,建议你在这个例子中选择 iPhone XR 模拟器作为目标设备。

使用 Modifier 描述 Text 及 Button

除了改变 Text 的文本内容外,我们会通过在 Text 声明之后使用方法调用的方式,来定义文本样式。让我们从单个的计算器按钮入手,比如加号键。将 ContentView 的 body 部分替换为以下内容:

var body: some View {
        Text("+")
              .font(.title) /// 1
              .foregroundColor(.white) /// 2
              .padding() /// 3
              .background(Color.orange) /// 4
}

font,foregroundColor,padding 和 background 各自定义了 Text 的一项属性:


image.png

font, foregroundColor, background 不用多说,padding将把当前的 View 包裹在一个新的 View 里,并在四周填充空白部分。在上面例子中,我们没有给定参数,这会在文本的四周都填上系统默认尺寸的空白。我们可以为 padding 指定需要填充的方向以及大小,比如:.padding(.top, 16) 将在上方填充 16 point 的空白,.padding(.horizontal, 8) 在水平方向 (也即 [.leading, .trailing]) 填充 8 point

上面的四种方法被成为View的Modifier,modifier 一般来说对顺序不敏感,对布局也不关心,它们更像是针对对象 View 本身的属性的修改。而与之相反,封装类的 modifier 的顺序十分重要。大致来说,view modifier 分为两种类别:

像是 font,foregroundColor 这样定义在具体类型 (比如例中的 Text) 上,然后返回同样类型 (Text) 的原地 modifier。
像是 padding,background 这样定义在 View extension 中,将原来的 View 进行包装并返回新的 View 的封装类 modifier。

基本布局

Stack 容器

横向排列的布局空间叫HStack,竖向排列的布局控件叫VStack, 叠加排列的布局控件叫ZStack,除了ZStack,H/V Stack 类似UIStackView的横向和纵向


struct CalculatorButton : View {
    let fontSize: CGFloat = 38
    let title: String
    let size: CGSize
    let backgroundColorName: String
    let action: () -> Void
    var body: some View {
        Button(action: action) {
            Text(title)
                .font(.system(size: fontSize))
                .foregroundColor(.white)
                .frame(width: size.width, height: size.height)
                .background(Color(backgroundColorName))
                .cornerRadius(size.width / 2)
        }
    }
}
这样一来,ContentView 的 body 里可以简化为:

var body: some View {
    HStack {
        CalculatorButton(
            title: "1",
            size: CGSize(width: 88, height: 88),
            backgroundColorName: "digitBackground")
        {
            print("Button: 1")
        }
        CalculatorButton(
            title: "2",
            size: CGSize(width: 88, height: 88),
            backgroundColorName: "digitBackground")
        {
            print("Button: 2")
        }
        CalculatorButton(
            title: "3",
            size: CGSize(width: 88, height: 88),
            backgroundColorName: "digitBackground")
        {
            print("Button: 3")
        }
        CalculatorButton(
            title: "+",
            size: CGSize(width: 88, height: 88),
            backgroundColorName: "operatorBackground")
        {
            print("Button: +")
        }
    }
}

ForEach

在项目中新建一个 Swift 文件,将它命名为 CalculatorButtonItem.swift,添加如下代码:

enum CalculatorButtonItem {
    enum Op: String {
        case plus = "+"
        case minus = "-"
        case divide = "÷"
        case multiply = "×"
        case equal = "="
    }
    enum Command: String {
        case clear = "AC"
        case flip = "+/-"
        case percent = "%"
    }
    case digit(Int)
    case dot
    case op(Op)
    case command(Command)
}

我们可以粗略地把计算器上的按钮分为四大类:代表从 0 至 9 的数字 digit,小数点 dot,加减乘除等号这样的操作 (Op),以及清空、符号翻转等这类命令 (Command)。接下来,可以在 extension 里追加定义必要的外观:

extension CalculatorButtonItem {
    var title: String {
        switch self {
        case .digit(let value): return String(value)
        case .dot: return "."
        case .op(let op): return op.rawValue
        case .command(let command): return command.rawValue
        }
    }
    var size: CGSize {
        CGSize(width: 88, height: 88)
    }
    var backgroundColorName: String {
        switch self {
        case .digit, .dot: return "digitBackground"
        case .op: return "operatorBackground"
        case .command: return "commandBackground"
        }
    }
}
///@propertyWrapper
struct RowView: View {
    let row: [CalculatorButtonItem]
    
    @Binding var brain: JiSuanQiState
    
    var body: some View {
        HStack {
            ForEach(row, id: \.self) { item in
                BtnView(
                    content: item.title,
                    backgroundcolorName: item.backgroundColorName,
                    size: item.size,
                    action: {
                        debugPrint("输出按钮:\(item.title)")
                        self.brain = self.brain.apply(item: item)
                })
            }
        }
    }
}


struct JiSuanqiButtonPad: View {
    let pad: [[CalculatorButtonItem]] = [
        [.command(.clear), .command(.flip),
         .command(.percent), .op(.divide)],
        [.digit(7), .digit(8), .digit(9), .op(.multiply)],
        [.digit(4), .digit(5), .digit(6), .op(.minus)],
        [.digit(1), .digit(2), .digit(3), .op(.plus)],
        [.digit(0), .dot, .op(.equal)]
    ]
    @Binding var brain: JiSuanQiState
    var body: some View {
        VStack(spacing: 8) {
            ForEach(pad①, id: \.self②) { row in
                RowView(row: row, brain: self.$brain)③
            }
        }
    }
}

我们需要对此进行一些解释。ForEach 是 SwiftUI 中一个用来列举元素,并生成对应 View collection 的类型。它接受一个数组,且数组中的元素需要满足 Identifiable 协议。如果数组元素不满足 Identifiable,我们可以使用 ForEach(_:id:) 来通过某个支持 Hashable 的 key path 获取一个等效的元素是 Identifiable 的数组。在我们的例子中,数组 row 中的元素类型 CalculatorButtonItem 是不遵守 Identifiable 的。为了解决这个问题,我们可以为 CalculatorButtonItem 加上 Hashable,这样就可以直接用 ForEach(row, id: .self) 的方式转换为可以接受的类型了。在 CalculatorButtonItem.swift 文件最后,加上一行:

extension CalculatorButtonItem: Hashable {}

使用 frame 或 Spacer 占满屏幕

frame: 中的 minWidth 和 maxWidth 为 Text 的宽度定义了范围,这会“提示” Text 不必遵守内容尺寸,而是去适应容器的尺寸。将 .infinity 传递给 maxWidth,表示不对最大宽度进行限制,这种情况下 Text 会尽可能占据它的容器的宽度,变为全屏宽。frame 还提供了一个 alignment 参数,让我们可以设定其对齐

Spacer: SwiftUI 允许我们定义可伸缩的空白:Spacer,它会尝试将可占据的空间全部填满。在我们的 body 中,可以加入一个 Spacer 来把 VStack 的上半部分全部填满

VStack(spacing: 12) {
    Spacer()
    Text("0")
        .font(.system(size: 76))
        .minimumScaleFactor(0.5)
        .padding(.trailing, 24)
        .lineLimit(1)
        .frame(minWidth: 0, maxWidth: .infinity, alignment: .trailing)
    CalculatorButtonPad()
        .padding(.bottom)
}

至此一个完整的计算器页面就算完成了


image.png

相关文章

  • SwiftUI学习整理(一)

    最前面想说的话 转眼已经是2020年10月了,2020已经过去大半了,上半年的计划已经完成,所以下半年的学习应该提...

  • SwiftUI学习整理(二)

    数据状态和绑定 上一篇文章没有涉及到如何使用数据让 app 界面真正能被使用。在 SwiftUI 里,用户界面是严...

  • SwiftUI(Combine)学习整理(三)

    如果有RxSwift的学习经验那么理解combine会更加迅速 通过对事件处理的操作进行组合 (combine) ...

  • swiftUI 常用控件介绍

    最近在学习swiftUI,首先来介绍一下swiftUI的一些基本用法,swiftUI和Flutter的语法比较类似...

  • SwiftUI 简介

    推荐学习SwiftUI官网[https://swiftui.jokerhub.cn]学习,下面大多是官网的解释,感...

  • swiftUI学习(一)

    @state属性:用@state声明以后,他会帮我们默认生成以下操作 自定义属性装饰器: 去使用

  • SwiftUI学习<一>

    1、command + option + p 刷新canvas画布 2、SwiftUI支持环境:iOS13+Swi...

  • JPDesignCode for iOS15

    SwiftUI学习项目 学自于国外一个很出名的SwiftUI课程:DesignCode[https://desig...

  • WWDC21 学习系列之 SwiftUI 支持将 Markdo

    新特性 SwiftUI 支持将 Markdown 直接传递给文本Text 示例代码 加入我们一起学习SwiftUI...

  • Swift5之开篇

    开始学习Swift5,记录一下学习的内容。 昨天开始看apple的文档,感受了下SwiftUI,SwiftUI5的...

网友评论

    本文标题:SwiftUI学习整理(一)

    本文链接:https://www.haomeiwen.com/subject/hdkfmktx.html