|
| 1 | ++++ |
| 2 | +title = "100 Days of SwiftUI" |
| 3 | +date = 2024-04-09T19:04:00-07:00 |
| 4 | +lastmod = 2024-04-09T22:16:43-07:00 |
| 5 | +tags = ["swift", "swiftui", "ios"] |
| 6 | +categories = ["swift", "swiftui", "ios"] |
| 7 | +draft = false |
| 8 | +toc = true |
| 9 | ++++ |
| 10 | + |
| 11 | +## <span class="section-num">1</span> 缘起 {#缘起} |
| 12 | + |
| 13 | +我花了半年多的时间,在工作之余的闲暇时间,学习了苹果的Swift语言和SwiftUI框架,想体现下IOS开发,再看下有没有机会通过写软件来做点副业。 <br/> |
| 14 | + |
| 15 | +先花了大概3个月时间,通过 [The Swift Programming Language](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/guidedtour/) 这本官方电子书[^fn:1]来学习Swift这门语言,又花了接近4个月的时候来学习 [100 Days of SwiftUI](https://www.hackingwithswift.com/100/swiftui) 这门课程[^fn:2],每天花费1到2小时来学习一课,总共100课,所以顾名思义叫 100 Days of SwiftUI. <br/> |
| 16 | + |
| 17 | +每完成一课,就在Twitter上发一条推文,今天刚好把第100天的推文发了. <br/> |
| 18 | + |
| 19 | +{{< figure src="/ox-hugo/tweet_day_100.png" >}} <br/> |
| 20 | + |
| 21 | +{{< figure src="/ox-hugo/tweet_day_99.png" >}} <br/> |
| 22 | + |
| 23 | +{{< figure src="/ox-hugo/tweet_day_96.png" >}} <br/> |
| 24 | + |
| 25 | +今天是结课之日,我通过了结课的考试,总分100分,考了91分,喜提课程证书一枚. <br/> |
| 26 | + |
| 27 | +{{< figure src="/ox-hugo/100_days_of_swiftui_certificate.jpg" >}} <br/> |
| 28 | + |
| 29 | +并在整个课程中,写了19个IOS App, 源码也基本放在[ GitHub](https://github.com/ramsayleung?tab=repositories&q=&type=&language=swift&sort=) [^fn:3]上了,不过所有的App都没有上架App Store,因为我还没有给苹果交税(99美刀的注册费). <br/> |
| 30 | + |
| 31 | +经过这100节课和19个APP的训练,我已经掌握了使用Swift和SwiftUI的基础开发技能,算是个入门的IOS开发了, 现在我可以说自己是前端,后端,数据开发,IOS开发都搞过的全栈(~~干~~)工程师了(不是) <br/> |
| 32 | + |
| 33 | +但是在苹果做出改变之前,我SwiftUI之旅可能就先到此为止了,原因下文再谈 <br/> |
| 34 | + |
| 35 | + |
| 36 | +## <span class="section-num">2</span> Swift 初体验 {#swift-初体验} |
| 37 | + |
| 38 | +Swift 是由LLVM之父 [Chris Lattner](https://en.wikipedia.org/wiki/Chris_Lattner) [^fn:4]在2010开始开发,在2014年的WWDC苹果开发者大会被苹果正式推出的一门编程语言。 <br/> |
| 39 | + |
| 40 | +按照官方的说法,Swift从Objective-C, Rust, Haskell, Ruby, Python, C# 都有不同程度的借鉴和学习。 <br/> |
| 41 | + |
| 42 | +因为我对上面提到的语言多少有涉猎,所以学习Swift起来基本没有什么困难,=Optional=, `Error Handling`, `Result`, `Generic`, `Enumerations`, `Protocol` 这些概念都和Rust的大同小异。 <br/> |
| 43 | + |
| 44 | +又因为是由LLVM之父来操刀,所以语言本身也设计得很优雅. <br/> |
| 45 | + |
| 46 | +让我眼前一亮的可能是借鉴自 [C# Extension Methods](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods) [^fn:5]的 `extension` 功能 , 可以对已有的 class, enum 或者是 protocol 类型增加新的函数,也就是在不修改源码的情况下,扩展已有的功能. <br/> |
| 47 | + |
| 48 | +例如,以下的代码就可以扩展内置的 `Double` 类型, 实现以米为单位,进行千米, 厘米,毫米,公尺的转换: <br/> |
| 49 | + |
| 50 | +```swift |
| 51 | +extension Double { |
| 52 | + var km: Double { return self * 1_000.0 } |
| 53 | + var m: Double { return self } |
| 54 | + var cm: Double { return self / 100.0 } |
| 55 | + var mm: Double { return self / 1_000.0 } |
| 56 | + var ft: Double { return self / 3.28084 } |
| 57 | +} |
| 58 | +let oneInch = 25.4.mm |
| 59 | +print("One inch is \(oneInch) meters") |
| 60 | +// Prints "One inch is 0.0254 meters" |
| 61 | +let threeFeet = 3.ft |
| 62 | +print("Three feet is \(threeFeet) meters") |
| 63 | +// Prints "Three feet is 0.914399970739201 meters" |
| 64 | +``` |
| 65 | + |
| 66 | +总体而言, Swift是一门吸收了众多PL理论的现代编程语言 <br/> |
| 67 | + |
| 68 | + |
| 69 | +## <span class="section-num">3</span> SwiftUI {#swiftui} |
| 70 | + |
| 71 | +SwiftUI 使用的声明式语法,让开发者写页面布局和效果变得简洁清晰, 例如通过 `VStack`, `HStack`, `ZStack` 就可以实现X轴,Y轴,和Z轴方向的布局 <br/> |
| 72 | + |
| 73 | +例如下面这个就是通过 `ZStack` 几行代码实现的叠加效果: <br/> |
| 74 | + |
| 75 | +```swift |
| 76 | + let colors: [Color] = |
| 77 | + [.red, .orange, .yellow, .green, .blue, .purple] |
| 78 | + |
| 79 | + |
| 80 | +var body: some View { |
| 81 | + ZStack { |
| 82 | + ForEach(0..<colors.count) { |
| 83 | + Rectangle() |
| 84 | + .fill(colors[$0]) |
| 85 | + .frame(width: 100, height: 100) |
| 86 | + .offset(x: CGFloat($0) * 10.0, |
| 87 | + y: CGFloat($0) * 10.0) |
| 88 | + } |
| 89 | + } |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +{{< figure src="/ox-hugo/zstack_rectangle.png" >}} <br/> |
| 94 | + |
| 95 | +除了声明式语法之外,SwiftUI让人赏心悦目的就是动画。好的动画在App里面绝对能起到画龙点睛的作用,而SwiftUI的内置动画已经非常强大了,下面就是使用内置动画实现的动画效果: <br/> |
| 96 | + |
| 97 | +```swift |
| 98 | +struct ContentView: View { |
| 99 | + @State private var dragAmount = CGSize.zero |
| 100 | + @State private var enable = false |
| 101 | + let letters = "Hello, World" |
| 102 | + var body: some View{ |
| 103 | + HStack(spacing: 0) { |
| 104 | + ForEach(0..<letters.count, id: \.self) { index in |
| 105 | + Text(String(letters[letters.index(letters.startIndex, offsetBy: index)])) |
| 106 | + .padding(5) |
| 107 | + .font(.title) |
| 108 | + .background(enable ? .green : .blue) |
| 109 | + .offset(dragAmount) |
| 110 | + .animation(.linear.delay(Double(index) / 20), value: dragAmount) |
| 111 | + } |
| 112 | + }.gesture( |
| 113 | + DragGesture() |
| 114 | + .onChanged { |
| 115 | + dragAmount = $0.translation |
| 116 | + } |
| 117 | + .onEnded { _ in |
| 118 | + dragAmount = CGSize.zero |
| 119 | + enable.toggle() |
| 120 | + } |
| 121 | + ) |
| 122 | + } |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +{{< figure src="/ox-hugo/drag_animation.gif" >}} <br/> |
| 127 | + |
| 128 | +```swift |
| 129 | + struct HeartBeatView: View { |
| 130 | + @State private var animationAmount = 1.0 |
| 131 | + var body: some View { |
| 132 | + Button("SOS"){ |
| 133 | + } |
| 134 | + .padding(50) |
| 135 | + .background(.red) |
| 136 | + .foregroundColor(.white) |
| 137 | + .clipShape(Circle()) |
| 138 | + .overlay( |
| 139 | + Circle() |
| 140 | + .stroke(.red) |
| 141 | + .scaleEffect(animationAmount) |
| 142 | + .opacity(2 - animationAmount) |
| 143 | + .animation(.easeOut(duration: 1) |
| 144 | + .repeatForever(autoreverses: false), value: animationAmount) |
| 145 | + ) |
| 146 | + .onAppear { |
| 147 | + animationAmount = 2 |
| 148 | + } |
| 149 | + } |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +{{< figure src="/ox-hugo/hearbeat.gif" >}} <br/> |
| 154 | + |
| 155 | +而Xcode 15新增的预览功能也很好用,可以让开发者不需要启动iPhone模拟器就能预览页面效果,节省了非常多的等待时间。 <br/> |
| 156 | + |
| 157 | +{{< figure src="/ox-hugo/xcode_preview.png" >}} <br/> |
| 158 | + |
| 159 | + |
| 160 | +## <span class="section-num">4</span> 问题 {#问题} |
| 161 | + |
| 162 | +听起来好像很美好, IDE新功能好用,编程语言优雅, UI框架简洁好用,但是苹果的开发思路却有大问题: 苹果开发的SwiftUI不向后兼容老版本的IOS。 <br/> |
| 163 | + |
| 164 | +SwiftUI大部分功能都是只支持IOS16及以后的版本,而苹果新出来的数据持久框架 `SwiftData` 甚至只支持IOS17, <br/> |
| 165 | +更离谱的是,SwiftUI的BugFix 也只支持高版本IOS, 这就意味着用户不升级IOS版本,甚至SwiftUI的bug开发者都没法修复。 <br/> |
| 166 | + |
| 167 | +不支持旧版本的IOS就让一大批的开发者和公司都没有动力去使用SwiftUI: <br/> |
| 168 | + |
| 169 | +对于开发新应用的开发者而言,只支持IOS17就意味着会流失一大群使用IOS16及以下版本的用户, <br/> |
| 170 | +而对于拥有存量用户的公司而言,更没有动力去使用SwiftUI,用了之后,旧版本IOS的用户可能直接无法打开应用。 <br/> |
| 171 | + |
| 172 | +因此SwiftUI就陷入了一个尴尬的境地,东西做得好,但是不会有人用; <br/> |
| 173 | + |
| 174 | +没有人自然就不用有人分享,宣传这门技术,自然就导致相关的学习资料非常匮乏, 进一步加深了初学者的学习难度; <br/> |
| 175 | + |
| 176 | +开发遇到问题连懂的人都不用,官方文档写了又约等于没有写, 直接劝退初学者,恶性循环。 <br/> |
| 177 | + |
| 178 | +又因为接受SwiftUI的开发者还不多,苹果版本迭代起来更加肆无忌惮,新版本又引入一堆的Breaking change,导致开发者更新版本非常痛苦. <br/> |
| 179 | + |
| 180 | +另外一个问题就是SwiftUI与苹果现有框架整合得不够好,如 `CoreImage` 框架,顾名思义是用来作图片处理. <br/> |
| 181 | + |
| 182 | +但之前是使用Objective-C写的,直接使用SwiftUI来调用,就会变成相当恶心,需要把Swift的数据结构传换成Objective-C来处理, 如: <br/> |
| 183 | + |
| 184 | +```swift |
| 185 | +func applyProcess(){ |
| 186 | + guard let outputImage = currentFilter.outputImage else {return} |
| 187 | + guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else{return} |
| 188 | + let uiImage = UIImage(cgImage: cgImage) |
| 189 | + processedImage = Image(uiImage: uiImage) |
| 190 | +} |
| 191 | + |
| 192 | +func loadImage() { |
| 193 | + Task{ |
| 194 | + guard let imageData = try await selectedItem?.loadTransferable(type: Data.self) else {return} |
| 195 | + |
| 196 | + guard let inputImage = UIImage(data: imageData) else {return} |
| 197 | + |
| 198 | + let beginImage = CIImage(image: inputImage) |
| 199 | + currentFilter.setValue(beginImage, forKey: kCIInputImageKey) |
| 200 | + applyProcess() |
| 201 | + } |
| 202 | +} |
| 203 | +``` |
| 204 | + |
| 205 | +把 `CoreImage` 框架的 `CIImage` 转成 `CoreGraphics` 框架的 `CGImage`, 然后再把 `CGImage` 转换成 `UIKit` 框架 `UIImage`, 然后再转换回SwiftUI 内置的 `Image` 类型, 可谓是相当麻烦了. <br/> |
| 206 | + |
| 207 | +但是对比SwiftUI只支持高版本的问题,Objective-C和Swift的互操作问题也只能算是恶心,但是起码有解决方法,对于前者,开发者是完全没法自行解决. <br/> |
| 208 | + |
| 209 | + |
| 210 | +## <span class="section-num">5</span> 总结 {#总结} |
| 211 | + |
| 212 | +过了一把野生IOS开发的瘾,但是除非是苹果愿意让SwiftUI支持低版本的IOS, <br/> |
| 213 | +不然我是不会继续使用SwiftUI来开发IOS了,受众太有限了,也没有太大的前景。 <br/> |
| 214 | + |
| 215 | +想要支持低版本的IOS,就只能走UIKit和Objective-C这条历史老路,我对此着实是望而生畏,有空还是学习点其他有趣的东西。 <br/> |
| 216 | + |
| 217 | +[^fn:1]: <https://docs.swift.org/swift-book/documentation/the-swift-programming-language/guidedtour/> <br/> |
| 218 | +[^fn:2]: <https://www.hackingwithswift.com/100/swiftui> <br/> |
| 219 | +[^fn:3]: <https://github.com/ramsayleung?tab=repositories&q=&type=&language=swift&sort>= <br/> |
| 220 | +[^fn:4]: <https://en.wikipedia.org/wiki/Chris_Lattner> <br/> |
| 221 | +[^fn:5]: <https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods> <br/> |
0 commit comments