心得感悟

起初看到 WWDC 上的演示 SwiftUI 时,我就觉得 SwiftUI 有种陌生的熟悉感(声明式语法),所以体验下,看看有没有什么启发。

先说下整体项目完成下来的感受:

  • 用 Swift + SwiftUI 开发 iOS 项目效率很高,本人之前没有接触过 Swift 语言,这次是从 0 开始学 swift 语言以及 swiftUI 框架的,每天花 2 个小时断断续续大致花了 3 天时间掌握了基本的 Swift 语法,而 SwiftUI 框架的掌握是按照官方的视频学习的(赞下 Apple 的文档、教学视频的完备性);
  • 从我一个前端开发工程师的视角看来,SwiftUI 使用的 “DSL”、状态管理和前端的 React 很相似,不少概念是相通的,比如 State,Props。
  • SwiftUI + Xcode11 + macOS Catalina 让研发效率大大提升,预览模式做到了所见即所得,注意这里是预览模式而不是模拟器模式
  • 借助于预览模式,UI 也可以做到很好的单页面的单元测试;这一点比较有意思,独立的 View 可以独立预览,然后可以注入数据,实时预览调试。

执行环境

  • macOS Mojave: 10.14.5
  • xcode: Version 11.0 beta 6 (11M392q)

项目信息

github: https://github.com/young-cowboy/swiftui-app-habits

App 效果预览

2 个关键点

这里提 2 个关键点“尾部闭包语法”以及“状态管理”,因为理解他们就基本能掌握 SwiftUI 来开发一个简单的项目。尾部闭包语法能让我们理解 SwiftUI 是如何通过声明式语法来创建 View 的,“数据传递”能让我们了解怎么实现数据流功能。

尾部闭包语法

Swift 有一个很重要的概念叫做“尾部闭包语法”,这种语法让 Swift DSL 的书写变得更“声明式”,我们先了解下 Swift 闭包定义:

  1. {(parameters) -> return type in
  2. // 代码
  3. }

这样来看是不是和 JavaScript 的闭包有一定的相似,但是闭包有很多种语法形式,这里我们举个例子说明下:

  1. import Foundation
  2. func wrapClosure (closure: (String, String) -> Void) {
  3. let name = "KK"
  4. let age = "18"
  5. closure(name, age)
  6. }

定义一个 function,参数只有一个,并且这个参数为一个函数,然后在方法体内部调用这个函数,传入参数;

  1. func foo (name: String, age: String) {
  2. print("My name is \(name) and \(age) years old")
  3. }
  4. wrapClosure(closure: foo) // My name is KK and 18 years old

通常我们会这样执行,定义个函数来处理传参。但是利用闭包的语法,有更简洁的方式,如下就一个闭包的语法了

  1. wrapClosure(closure: { (name: String, age: String) in
  2. print("My name is \(name) and \(age) years old")
  3. })

还可以利用 Swift 的类型推断能力,不写闭包参数的类型,如下

  1. wrapClosure(closure: { name, age in
  2. print("My name is \(name) and \(age) years old")
  3. })

还可以利用快捷参数名来获取参数,如下

  1. wrapClosure(closure: {
  2. print("My name is \($0) and \($1) years old")
  3. })

最后还有更简洁的方式,这样是 SwiftUI 利用到的一个特性,所以SwiftUI 的 DSL 看起来很有声明式的感觉,这个特性叫 “尾部闭包语法”,这次方法执行连括号都可以不写了。

如果一个闭包是以一个函数的最后一个参数传递的,那么它就可以在函数的圆括号以外内联。

  1. wrapClosure {
  2. print("My name is \($0) and \($1) years old")
  3. }

到此应该能看出为什么 SwiftUI 能用声明式的语法来创建各种 View 协议的视图了。

数据传递

在做面向用户的功能时,一个很主要的点是数据传递,不同的设计理念解决特点场景的问题。这里简单介绍下 SwiftUI 的数据怎样传递,这里有一个概念property wrapper,我理解是装饰器 decorate,介绍三个装饰器 @State@Binding@ObservedObject@Published

  • @State 装饰过的属性发生了变化,SwiftUI 会根据新的属性值重新创建视图
  • @Binding 修饰器修饰后,属性变成了一个引用类型,传递变成了引用传递,这样父子视图的状态就能关联起
  • @ObservedObject 修饰一个复杂类型数据,可以被多个 View 所使用,用 @Published 修饰对象里属性,表示需要被监听;

更多的细节可以参考:https://mecid.github.io/2019/06/12/understanding-property-wrappers-in-swiftui/

页面结构设计

一共有 4 个页面 HabitListView, AddButtonView, HabitDetailView, AddItemView

数据结构设计

项目涉及到三个数据结构: HabitItem(习惯项), HabitIconArray(习惯图标), HabitColor(习惯主题)

HabitColor 主题色,保存了习惯的主题

  1. public let UserColorArray = [
  2. Color(red:75 / 255, green:166 / 255, blue: 239 / 255),
  3. Color(red:161 / 255, green:206 / 255, blue: 97 / 255),
  4. Color(red:248 / 255, green:214 / 255, blue: 80 / 255),
  5. Color(red:243 / 255, green:176 / 255, blue: 74 / 255),
  6. Color(red:238 / 255, green:140 / 255, blue: 111 / 255),
  7. Color(red:237 / 255, green:113 / 255, blue: 165 / 255),
  8. Color(red:207 / 255, green:102 / 255, blue: 247 / 255),
  9. Color(red:77 / 255, green:110 / 255, blue: 247 / 255),
  10. Color(red:236 / 255, green:107 / 255, blue: 102 / 255)
  11. ]

HabitIconArray 习惯的图标库

  1. public let IconNameArray: [String] = [
  2. "alarm",
  3. "book",
  4. "pencil",
  5. "desktopcomputer",
  6. "gamecontroller",
  7. "sportscourt",
  8. "lightbulb"
  9. ]

HabitItem 用来保存习惯的详细信息,这里实现 ObservableObject 协议,用来告诉 SwiftUI 这个对象需要监听,用 Published property wrapper 包装了 checkList 属性,表示这个属性是要监听的,因为它可能需要传递给子 View

  1. class HabitItem: Identifiable, ObservableObject {
  2. var name: String = ""
  3. var iconName: String = "clock"
  4. var theme: Color = UserColor.color1.value
  5. var uuid: Int = 0
  6. @Published var checkList: [Bool] = [false, false, false, false, false, false, false]
  7. init () {
  8. }
  9. init(name: String, iconName: String, theme: Color) {
  10. self.name = name
  11. self.iconName = iconName
  12. self.theme = theme
  13. self.uuid = generatteID()
  14. }
  15. }

UI 设置

具体代码参考仓库代码,这里讲解一个流程,新建一个“习惯”项,在 MainView 里

MainView

  1. ...
  2. @State var sheetVisible: Bool = false
  3. @State var sheetType: String = "add"
  4. ...
  5. ...
  6. AddButtonView() {
  7. self.sheetType = "add"
  8. self.sheetVisible = true
  9. }
  10. ...

AddButtonView 利用尾部闭包语法内联了一个闭包用来相应事件

AddButtonView 里定义了 onPressed 属性

  1. struct AddButtonView: View {
  2. var onPressed: () -> Void
  3. var body: some View {
  4. Button(action: {
  5. self.onPressed()
  6. }) {
  7. HStack {
  8. Image(systemName: "plus.circle.fill")
  9. .resizable()
  10. .frame(width: 60, height: 60)
  11. .foregroundColor(Color.blue)
  12. }
  13. }
  14. }
  15. }

AddItemView 里的新增按钮点击后相应事件,把选中的数据传递 onSumit 属性回调里

  1. struct AddItemView: View {
  2. @State var newItemTitle = ""
  3. @State var selectIconIndex: Int = 0
  4. @State var selectColorIndex: Int = 0
  5. var onSumit: (HabitItem) -> Void
  6. var onDissmis: () -> Void
  7. var body: some View {
  8. VStack {
  9. ...
  10. ...
  11. VStack {
  12. Button(action: {
  13. if self.newItemTitle != "" {
  14. let iconName = IconNameArray[self.selectIconIndex];
  15. let theme = UserColorArray[self.selectColorIndex];
  16. self.onSumit(HabitItem(name: self.newItemTitle, iconName: iconName, theme: theme))
  17. }
  18. }) {
  19. Text("新增").frame(minWidth: 0, maxWidth: .infinity)
  20. }
  21. ...
  22. }
  23. ...
  24. }
  25. ...
  26. }
  27. }
  28. }

在 MainView 里,利用闭包处理回调事件新增选项

  1. AddItemView(onSumit: { item in
  2. self.habitItemList.insert(item, at: 0)
  3. self.sheetVisible = false
  4. }, onDissmis: { self.sheetVisible = false })

剩下的功能大同小异,可以把代码拉下来本地运行看看效果,github: https://github.com/young-cowboy/swiftui-app-habits

谢谢

SwiftUI 实战:从 0 到 1 研发一个 App的更多相关文章

  1. 用weexplus从0到1写一个app

    说明 基于wexplus开发app是来新公司才接触的,之前只是用过weex体验过写demo,当时就被用vue技术栈来开发app的开发体验惊艳到了,这个开发体验比react native要好很多,对于我 ...

  2. 用weexplus从0到1写一个app(2)-页面跳转和文章列表及文章详情的编写

    说明 结束连续几天的加班,最近的项目终于告一段落,今天抽点时间开始继续写我这篇拖了很久的<用weexplus从0到1写一个app>系列文章.写这篇文章的时候,weexplus的作者已经把w ...

  3. [3.0] 一个人开发一个App,小程序从0到1,删减添加

    在这个黄道吉日,咱们将要干一件,惊天地泣鬼神,妇孺皆知的大事,那就是删掉微信开发工具自动生成的源代码. 删掉pages下的index.logs目录,啥都不留: 删掉utils下的util.js,只流空 ...

  4. 开发一个App的成本是多少?

    英文出处:savvyapps.欢迎加入翻译小组. 在最近的一个会议上,一个叫Bob的老顾客引用了<App Savvy>(<放飞App:移动产品经理实战指南>)中探讨研发一个io ...

  5. Android Studio 1.0.2项目实战——从一个APP的开发过程认识Android Studio

    Android Studio 1.0.1刚刚发布不久,谷歌紧接着发布了Android Studio 1.0.2版本,和1.0.0一样,是一个Bug修复版本.在上一篇Android Studio 1.0 ...

  6. 实战:使用SVN+apache搭建一个版本控制服务器

    今天讲的内容: 实战:使用SVN+apache搭建一个版本控制服务器 每天: 10:00 晚上:21:00 服务端:xuegod63.cn   IP:192.168.10.63 服务概述: SVN(s ...

  7. 零基础入门 实战mpvue2.0多端小程序框架

    第1章 课程快速预览(必看!!!)在这一章节中,老师讲带领你快速预览课程整体.其中,涉及到为什么要做这么一门实战课程.制作一个小程序的完整流程是怎么样的,以及如何做项目的技术选型. 第2章 30 分钟 ...

  8. 编译原理实战——使用Lex/Flex进行编写一个有一定词汇量的词法分析器

    编译原理实战--使用Lex/Flex进行编写一个有一定词汇量的词法分析器 by steve yu 2019.9.30 参考文档:1.https://blog.csdn.net/mist14/artic ...

  9. Swift3.0语言教程获得一个公共的前缀

    Swift3.0语言教程获得一个公共的前缀 Swift3.0语言教程获得一个公共的前缀,当在一个程序中有多个字符串时,我们需要判断是否有两个字符串有公共的前缀时,是很困难的.在NSString中的co ...

随机推荐

  1. .Net手动实现ORM及代码生自动成器

    序言 代码生成器 同时提供便捷的开发管理功能和多项开发工作中常用到的辅助工具功能,您可以很方便轻松地进行项目开发,让软件开发变得轻松而快乐!帮您快速开发项目,缩短开发周期,减少开发成本,大大提高了企业 ...

  2. POJ 1743 Musical Theme ( 后缀数组 && 最长不重叠相似子串 )

    题意 : 给 n 个数组成的串,求是否有多个“相似”且不重叠的子串的长度大于等于5,两个子串相似当且仅当长度相等且每一位的数字差都相等. 分析 :  根据题目对于 “ 相似 ” 串的定义,我们可以将原 ...

  3. 给字体和元素加阴影text-shadow和box-shadow

    1.语法:  对象选择器 {text-shadow:X轴偏移量 Y轴偏移量 阴影模糊半径 阴影颜色} 注:text-shadow可以使用一个或多个投影,如果使用多个投影时必须需要用逗号“,”分开. 2 ...

  4. Conturbatio

    Conturbatio Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total ...

  5. Codeforces Gym 100269 Dwarf Tower (最短路)

    题目连接: http://codeforces.com/gym/100269/attachments Description Little Vasya is playing a new game na ...

  6. iOS即时通讯之CocoaAsyncSocket源码解析五

    接上篇:iOS即时通讯之CocoaAsyncSocket源码解析四         原文 前言: 本文为CocoaAsyncSocket Read篇终,将重点涉及该框架是如何利用缓冲区对数据进行读取. ...

  7. 线性代数之——SVD 分解

    SVD 分解是线性代数的一大亮点. 1. SVD 分解 \(A\) 是任意的 \(m×n\) 矩阵,它的秩为 \(r\),我们要对其进行对角化,但不是通过 \(S^{-1}A S\).\(S\) 中的 ...

  8. java SimpleDateFormat setLenient用法

    参考博客:https://www.cnblogs.com/my-king/p/4276577.html SimpleDateFormat.setLenient(true) : 默认值true,不严格解 ...

  9. C# 模拟登陆

    原理 我们知道,一般需要登录的网站,服务器和客户端都会有一段时间的会话保持,而这个会话保持是在登录时候建立的, 服务端和客户端都会持有这个KEY,在后续访问时,都需要核对这两个KEY是否一致. 而客户 ...

  10. K近邻实战手写数字识别

    1.导包 import numpy as np import operator from os import listdir from sklearn.neighbors import KNeighb ...