Xcode11、Swift5.1新特性以及SwiftUI初探


https://www.jianshu.com/p/bcf1c3135964

2019.06.26 09:29:25字數 1754閱讀 794

1.What's new in Xcode 11

1.1 Add Editor

在 Xcode 11 之前,利用 Xcode 我們只能通過打開 Assistant Editor 同時查看兩個代碼文件。

Add Editor 按鈕位於 Editor Options 按鈕的旁邊,默認狀態下是在右邊添加新的 Editor,當我們按住 Option 的時候就可以切換添加的方向。

 
Vcs45d.png

Destination Chooser 來打開新的 Editor 或者替換已有的 Editor,方法是按住 Shift + Option 后,點擊左側 Project Navigator 中的某一個文件,Destination Chooser 就會出現,我們可以使用上下左右鍵或者是鼠標來控制他.

當我們打開了很多個 Editor 的時候,如果我們想專注於某個 Editor 中的內容,我們也可以將某個 Editor 放大.

1.2 Source Minimap

如果我們在 Swift 中使用 // Mark: 來為代碼做分段,那么 // Mark:后面的內容在 Source Minimap 中會放大展示出來

 
VgPvT0.png

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

 
VgPJWF.png

1.3 Swift Pacakge Manager

並且 Xcode 11 也內置了 SPM,並在 File -> Swift Pacakge Manager 中提供了一些基本的 SPM 的操作
然后我們就會看到這樣的一個面板(需要在 Xocde 中登錄 Github、Bitbucket 或者 Gitlab 的賬號)

添加完成后就能在左側看到添加的第三方庫,並可以導入使用了。

 

 
Vg30iD.png

 

1.4 Debug

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

 

 
Vg8476.png

 

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

 
V6Ld2V.png

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)) } } 

 

 
V2vort.png

 

也可以不同尺寸的設備進行預覽

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) } } } 

 

 
VR9D7q.png

 

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 方法中。這樣做有兩個好處,首先可以提高預覽結果呈現的速度,其次在應用進入后台時,不會做一些額外的操作,只會做一些必要的操作,使應用快速進入睡眠狀態,以節省耗電.

 
VfKESH.jpg

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 的子類,所以需要切換到實時模式才能看到地圖。

 

 
V2LTtH.png

 

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")) } } } 

 

 
VRkSsg.png

 

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")) } } } 

 

 
VRUI41.png

 

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

 
VRN2TA.png

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM