https://www.jianshu.com/p/bcf1c3135964
1.What's new in Xcode 11
1.1 Add Editor
在 Xcode 11 之前,利用 Xcode 我們只能通過打開 Assistant Editor 同時查看兩個代碼文件。
Add Editor 按鈕位於 Editor Options 按鈕的旁邊,默認狀態下是在右邊添加新的 Editor,當我們按住 Option 的時候就可以切換添加的方向。

Destination Chooser 來打開新的 Editor 或者替換已有的 Editor,方法是按住 Shift + Option 后,點擊左側 Project Navigator 中的某一個文件,Destination Chooser 就會出現,我們可以使用上下左右鍵或者是鼠標來控制他.
當我們打開了很多個 Editor 的時候,如果我們想專注於某個 Editor 中的內容,我們也可以將某個 Editor 放大.
1.2 Source Minimap
如果我們在 Swift 中使用 // Mark: 來為代碼做分段,那么 // Mark:后面的內容在 Source Minimap 中會放大展示出來

如果你把鼠標放到 Source Minimap 的時候按住 Command 鍵,你就會看到 Source Minimap 會展示出來當前文件的所有摘要信息

1.3 Swift Pacakge Manager
並且 Xcode 11 也內置了 SPM,並在 File -> Swift Pacakge Manager 中提供了一些基本的 SPM 的操作
然后我們就會看到這樣的一個面板(需要在 Xocde 中登錄 Github、Bitbucket 或者 Gitlab 的賬號)
添加完成后就能在左側看到添加的第三方庫,並可以導入使用了。

1.4 Debug
在 Xcode 11 之前,如果我們想在真機中模擬弱網或者模擬設備在高溫、低溫等極端條件下的運行情況,我們需要求助於設置選項中的開發者選項,操作起來相對比較麻煩。在 Xcode 11 中,我們可以通過 Devices 界面中的 Device Conditions 面板,在 Xcode 中操作這一切.

2 What's new in Swift 5.1
2.1 Implicit returns from single-expression functions
如果一個閉包只包含一個表達式,那么可以把 return 省略掉,隱式返回該表達式(我們經常見到的map, filter, flatmap 這些高階函數經常用到這個特性)。在 Swift 5.1 中,一個函數只包含一個表達式,也可以將 return 省略掉。
extension Sequence where Element == Int { func sum() -> Element { return reduce(0, +) } }
func sum() -> Element { reduce(0, +) }
2.2 Synthesize default values for the memberwise initializer
一個 Struct 的各個屬性有默認值時,編譯器會基於屬性逐一去生成初始化方法
struct Dog { var age: Int = 1 var name: String = "xiaohuang" var color: String }
let dog = Dog(age: 1, color: "1")
let dog1 = Dog(color: "1")
let dog3 = Dog(age: 1, name: "", color: "")
2.3 Property Delegate
WWDC上寫的是@propertyWrapper,在 Xcode 11 beta 1是@propertyDelegate
static var isFooFeatureEnabled: Bool { get { return UserDefaults.standard.bool(forKey: "FOO_FEATURE_ENABLED") } set { UserDefaults.standard.set(newValue, forKey: "BAR_FEATURE_ENABLED") } } static var isBarFeatureEnabled: Bool { get { return UserDefaults.standard.bool(forKey: "LOGGED_IN") } set { UserDefaults.standard.set(newValue, forKey: "LOGGED_IN") } }
@propertyDelegate
struct UserDefault<T> { let key: String let defaultValue: T var value: T { get { return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } set { UserDefaults.standard.set(newValue, forKey: key) } } }
enum GlobalSettings { @UserDefault(key: "FOO_FEATURE_ENABLED", defaultValue: false) static var isFooFeatureEnabled: Bool @UserDefault(key: "BAR_FEATURE_ENABLED", defaultValue: false) static var isBarFeatureEnabled: Bool }
展開相當於
enum GlobalSettings { static var $isFooFeatureEnabled = UserDefault<Bool>(key: "FOO_FEATURE_ENABLED", defaultValue: false) static var isFirstLanch: Bool { get { return $isFooFeatureEnabled.value } set { $isFooFeatureEnabled.value = newValue } } }
2.4 DSLs
HTML 代碼, 但是對於 Swift 編譯器來說,只是一個長文本,所以無法進行語法檢查,
所以發明了一種 DSL ,期望大家使用 Xcode 寫 HTML 的時候用下面這種 DSL 來寫
html {
head {
title("\(name)'s WWDC19 Blog") }.
body {
h2 { "Welcome to \(name)'s WWDC19 Blog!" }
br()
"Check out the talk schedule and latest news from "
a{
"the source" }.href(“https://developer.apple.com/wwdc19/")
}
}
DSL 用於 SwiftUI

2.5 Opaque Result Types
protocol Shape {}
struct Rectangle: Shape {}
struct Union<A: Shape, B: Shape>: Shape {
var a: Shape
var b: Shape
}
struct Transformed<S: Shape>: Shape {
var shape: S
}
protocol GameObject {
associatedtype ShapeType: Shape
var shape: ShapeType { get }
}
struct EightPointedStar: GameObject {
var shape: Union<Rectangle, Transformed<Rectangle>> {
return Union(a:Rectangle(), b:Transformed(shape: Rectangle()))
}
}
上述代碼是可以編譯通過的,但是 EightPointedStar 的 Shape 返回類型又臭又長,被暴露了出去;如果換成 Shape 則編譯不通過,原因是 associatedtype ShapeType 要求必須指定具體的類型,而 Shape 只是協議。
假如 Shape 協議中含有 Self 或者 associatedtype,無法作為函數的返回參數。
struct EightPointedStar: GameObject {
var shape: some Shape {
return Union(a:Rectangle(), b:Transformed(shape: Rectangle()))
}
}
-
語法上隱藏具體類型,所以叫做不透明結果類型
-
強類型:類型參數不丟失
-
允許帶有 Self 或者 associatedtype 的協議作為返回類型
swiftUI 中的使用
public protocol View : _View { associatedtype Body : View var body: Self.Body { get } }
含有 associatedtype 所以 View 不能直接作為類型來使用 前面加上some
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
3 SwiftUI
SwiftUI 是一個搭載在用戶 iOS 系統上的 Swift 框架。因此它的最低支持的版本是 iOS 13.
3.1 聲明式的界面開發方式
傳統的 UIKit,我們寫UI的步驟是“創建 label”,“設置文字”,“將其添加到 view 上”,這種屬於命令式。
聲明式告訴只需要告訴 View 上有一個文本標簽以及文字是什么。然后 SwiftUI 會根據你的聲明進行渲染。這些聲明只是純數據結構的描述,而不是實際顯示出來的視圖。如果綁定了狀態,當狀態發生改變時,框架重新調用聲明部分的代碼,計算出新的 view 聲明,並和原來的 view 進行差分,之后框架負責對變更的部分進行重新繪制。
3.2 app啟動部分
新創建的 SwiftUI 項目中,UISceneConfigurations 通過這個管理啟動的。
func application( _ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) }
func scene( _ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UIHostingController(rootView: ContentView()) self.window = window window.makeKeyAndVisible() }
3.3 在 Xcode 中實時預覽
需要使用搭載有 SwiftUI.framework 的 macOS 10.15 才能夠看到 Xcode Previews 界面。
SwiftUI 文件中,下面結構體聲明該 view 的預覽。代碼改變和預覽界面改變兩邊都會實時更改。(視圖結構改變需要重新刷新)
struct ContentView_Previews : PreviewProvider { static var previews: some View { ContentView() } }
可以一個改變預覽視圖的大小和個數
struct LandmarkRow_Previews: PreviewProvider { static var previews: some View { Group { LandmarkRow(landmark: landmarkData[0]) LandmarkRow(landmark: landmarkData[1]) } .previewLayout(.fixed(width: 300, height: 70)) } }

也可以不同尺寸的設備進行預覽
struct LandmarkList_Previews : PreviewProvider { static var previews: some View { ForEach(["iPhone SE", "iPhone XS Max"].identified(by: \.self)) { deviceName in LandmarkList() .previewDevice(PreviewDevice(rawValue: deviceName)) .previewDisplayName(deviceName) } } }

Apple 建議在編寫 Previews 應用程序時,將普通 Model 用 View Model 進行封裝,View Model 中只保留需要的數據字段,並根據顯示做一些合並或者其他的處理。這不僅能使程序更加簡明也能夠更方便的編寫測試用例。降低因為數據錯誤而導致布局錯誤的可能性。
class Animal { let commonName: String let class: String let genus: String let species: String weak var clade: Clade? let genome: Genome let imageName: String } // AnimalCellModel -- AnimalCell's View Model struct AnimalCellModel { let commonName: String let class: String let scientificName: String let image: String? } extension AnimalCellModel { init(for animal: Animal) { self.commonName = animal.commonName self.scientificName = "\(animal.genus) \(animal.species)" ... } } // AnimalCell struct AnimalCell : View { let model: AnimalCellModel var body: some View { HStack { Image(model.image) VStack { Text(model.commonName).font(.title).fontWeight(.bold) Text(model.familyName) } Text(model.scientificName).italic() } } } // AnimalCellPreviews struct AnimalCellPreviews : PreviewProvider { static let models: [AnimalCellModel] static var previews: some View { ForEach(models.identified(\.self)) { model in AnimalCell(model: model) } } } // Test Case class AnimalCellModelTest : XCTestCase { func testRedFox() { let fox: Animal = ... let foxModel = AnimalCellModel(for: fox) XCTAssertEqual(foxModel.commonName, "Red fox") XCTAssertEqual(foxModel.scientificName, "Vulpus vulpus") } }
Apple 建議我們把視圖層級相關的方法從 didFinishLanchingWithOptions 遷移到 SceneDelegate 中的 willConnectTo 方法中。這樣做有兩個好處,首先可以提高預覽結果呈現的速度,其次在應用進入后台時,不會做一些額外的操作,只會做一些必要的操作,使應用快速進入睡眠狀態,以節省耗電.

3.4 UIKit 和 SwiftUI
在 SwiftUI 中使用 UIView 子類,需要將其他 view 包裝在遵循 UIViewRepresentable 協議的 SwiftUI view 中。
UIViewRepresentable 協議需要實現兩個方法: makeUIView(context:) 用來創建一個 MKMapView, updateUIView(_:context:) 用來配置 view 並響應修改。
typealias UIViewType = MKMapView func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView { return MKMapView(frame: .zero) } func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<MapView>) { let coordinate = CLLocationCoordinate2D( latitude: 34.011286, longitude: -116.166868) let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0) let region = MKCoordinateRegion(center: coordinate, span: span) uiView.setRegion(region, animated: true) }
當預覽處於 static mode 時僅顯示 SwiftUI view 。因為 MKMapView 是一個 UIView 的子類,所以需要切換到實時模式才能看到地圖。

3.5 列表與導航
- NavigationView 導航
- List 相當於tableview
- landmarkData 列表中的數據
- NavigationButton 列表中每個 row 的導航跳轉按鈕
- LandmarkRow 相當於tableviewcell
struct LandmarkList : View { var body: some View { NavigationView { List(landmarkData) { landmark in NavigationButton(destination: LandmarkDetail(landmark: landmark)) { LandmarkRow(landmark: landmark) } } .navigationBarTitle(Text("Landmarks")) } } }

3.6 狀態
官方教程中的狀態控制列表的例子
struct LandmarkList: View { @State var showFavoritesOnly = true var body: some View { NavigationView { List { Toggle(isOn: $showFavoritesOnly) { Text("Favorites only") } ForEach(landmarkData) { landmark in if !self.showFavoritesOnly || landmark.isFavorite { NavigationButton(destination: LandmarkDetail(landmark: landmark)) { LandmarkRow(landmark: landmark) } } } } .navigationBarTitle(Text("Landmarks")) } } }

@State 修飾符是一個 @propertyDelegate struct State<Value>

上面代碼中的 $showFavoritesOnly 是 showFavoritesOnly.binding 的簡化。binding 將創建一個 showFavoritesOnly 的引用,並將它傳遞給 Toggle 。Toggle 中對它的修改,會直接反應到當前 View 的 showFavoritesOnly 去設置它的 value。而 State 的 value didSet 將觸發 body 的刷新,從而完成 State -> View 的綁定。