打通SwiftUI任督二脈

一眼万年的星空發表於2024-02-28

swiftUI

序言

開年的第一篇文章,今天分享的是SwiftUI,SwiftUI出來好幾年,之前一直沒學習,所以現在才開始;如果大家還留在 iOS 開發,這們語言也是一個趨勢; 目前待業中.... 不得不說已逝的2023年,大家開始都抱著一解封,經濟都會向上轉好,可是現實不是我們想象那樣;目前我也在學習 SwiftUI,並且努力找工作中....;至於 2024 年經濟如何,咱們作為老百姓在大環境和全球經濟影響下;坦然面對,提升自己。 這裡不得不說國人堅韌不拔的精神。“卷” -- 努力吧Coding人

SwiftUI體驗

Xcode建立專案之後出現工程預設建立的UI介面;如下

swiftUI

一開始心裡對自己說:"SwiftUI作為iOS開發新的UI體系,為啥初創的專案這麼多程式碼,給初學者看到,一種壓迫感,心想這語法好複雜,不想學了";不管你是不是這樣心裡,我剛開始看見,這麼一坨程式碼,沒什麼心思,於是索性刪掉;按自己能理解學習的方式來操作;於是做了簡化:

import SwiftUI
import SwiftData

struct ContentView: View {
   
    var body: some View {
        Text("hello,word")
    }
}

#Preview {
    ContentView()
        .modelContainer(for: Item.self, inMemory: true)
}

關鍵字 some

關鍵字some啥玩意兒,完全陌生;先看看View;點選進入原始碼結構檢視:

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol View {

    /// The type of view representing the body of this view.
    ///
    /// When you create a custom view, Swift infers this type from your
    /// implementation of the required ``View/body-swift.property`` property.
    associatedtype Body : View

    @ViewBuilder @MainActor var body: Self.Body { get }
}

一堆英文註解估計大家不喜歡看,我就沒貼出來了;簡單來說:
View 是一個泛型協議,它定義了所有檢視型別需要遵循的介面,透過some修飾;表示 "我返回一個滿足View 協議的某種型別"。some關鍵字告訴 Swift,雖然我們知道body必須返回一個View,但我們不確定具體是哪種 View(例如,Text, Image, VStack 等)。

協議裡有一個associatedtypebody,其實這種協議就是當作約束形式使用;只要遵守這種協議編譯器每次閉包中返回的一定是一個確定,遵守View協議的型別。

那麼蘋果工程師利用Swift5.1 Opaque return types特性,開發者提供了一個靈活的開發模式,抹掉了具體的型別,不需要修改公共API來確定每次閉包的返回型別,也降低了程式碼書寫難度。(學學蘋果那些大神思想,不錯)

在來看看Preview

struct ContentView_Previews:PreviewProvider{
    static var previews: some View{
        ContentView()
        
    }
}

PreviewProvider就一個協議類,它的額作用提供swiftUI不用執行,就能直接看到UI渲染變化,我覺得這個挺好,減少開發人員對UI執行測試次數和時間,而previews就是一個靜態屬性,返回一個 View 物件,用於在預覽皮膚中展示。

@State屬性包裝器

@State屬性包裝器解決UI介面上,資料同步以及及時重新整理的功能。一般來說資料更新完,介面 UI 同時更新。在 SwiftUI裡面,檢視中宣告的任何狀態、內容和佈局,源頭一旦發生改變,會自動更新檢視,因此,只需要一次佈局,這個時候出現了@State,它來解決與UI之間資料狀態問題。

它的概念就是:@State 是一個屬性包裝器(property wrapper),用於宣告狀態屬性(state property)
當狀態屬性發生變化時,SwiftUI 會自動更新檢視以反映最新的狀態。

屬性的值被儲存在特殊的記憶體區域中,這個區域與 View struct 是隔離的 至於被它修飾的屬性記憶體儲存與分佈現在無從得知,還沒學習到那麼深入,這事兒慢慢來,不是一天兩天的,先上個程式碼看看它怎麼使用的:

import SwiftUI

struct StateBootcamp: View {
    
    @State var bgkColor:Color = Color.blue
    @State var cut:Int = 0
    
    var body: some View {
        
        ZStack{
            
            bgkColor
                .ignoresSafeArea(.all)
            
            VStack(spacing: 20){
                
                Text("Hello, World!")
                    .font(.title)
                
                Text("count:\(cut)")
                    .font(.largeTitle)
                
                HStack(spacing: 20){
                    Button("Button01") {
                        cut+=1
                        bgkColor = Color.red
                    }
                    .font(.title)
                    .foregroundColor(.white)
                    
                    Button("Button02") {
                        cut-=1
                        bgkColor = .purple
                    }
                    .font(.title)
                    .foregroundColor(.white)
                }
                Button("預設"){
                    cut=0
                    bgkColor = .blue
                }
                .font(.title)
                .foregroundColor(.white)
            }
        }
    }
}

#Preview {
    StateBootcamp()
}

其實一看程式碼,就一幕瞭然,知道它的使用與作用;如果你寫過swift程式碼,這些東西很好理解,但是隻會OC,那麼我建議你學習下swift;在來看swiftUI語法糖才更好理解。

在看看原始碼:

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen @propertyWrapper public struct State<Value> : DynamicProperty {
    public init(wrappedValue value: Value)
    public init(initialValue value: Value)
    public var wrappedValue: Value { get nonmutating set }
    public var projectedValue: Binding<Value> { get }
}


@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension State where Value : ExpressibleByNilLiteral {

    /// Creates a state property without an initial value.
    ///
    /// This initializer behaves like the ``init(wrappedValue:)`` initializer
    /// with an input of `nil`. See that initializer for more information.
    @inlinable public init()
}


可以看到State是一個結構體,由@propertyWrapper包裝的。@propertyWrapper是屬性包裝器。property wrapper 做的事情大體如下:

-   為底層的儲存變數`State<Int>`自動提供一組 **getter** 和 **setter** 方法,結構體內儲存了`Int`的具體數值;

-   在 body 首次求值前,將`State<Int>`關聯到當前`View`上,為它在堆中對應當前`View`分配一個儲存位置。

-   為`@State`修飾的變數設定觀察,當值改變時,觸發新一次的`body`求值,並重新整理 UI。

SwiftUI基礎元件

Spacer墊片:先貼貼程式碼

import SwiftUI

struct SpacerBootcampDemo: View {
    var body: some View {
        Text("Spacer UP")
            .font(.largeTitle)
        
        Spacer()
            .frame(width: 37)
            .background(.blue)
        
        Text("Spacer Down")
            .font(.largeTitle)
        
    }
}

#Preview {
    SpacerBootcampDemo()
}

在看看效果圖:

Spacer

總結:Spacer 是一個靈活的空間檢視,它的主要作用是在佈局中自動調整自身的高度和寬度,以填滿特定的空間;簡單來說,它就是一個墊片,調整自身檢視的高度,如果它周圍有其他檢視,也會受到Spacer影響。

ScrollView 如果你之前使用UIkit框架開發,在用SwiftUI,一下有點不適應,程式碼和之前的 UIkit 開發模式不太一樣,但是大大縮短UI編寫時間;先上程式碼:


import SwiftUI

struct ScollViewBootcamp: View {
    
    var body: some View {
        
        ScrollView{
            LazyVStack{
                ForEach(0..<20){
                    (idx) in
                    
                    VStack {
                        
                        Text("Hello, World!")
                            .font(.title)
                            .foregroundStyle(.white)
                            .frame(width: UIScreen.main.bounds.width-20,height: 350)
                            .background(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0..<215)/255.0, green: CGFloat.random(in: 0..<235)/255.0, blue: CGFloat.random(in: 0...247)/255.0, alpha: 0.9)))
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                        
                        Rectangle()
                            .fill(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0...187)/255.0, green: CGFloat.random(in: 0..<210)/255.0, blue: CGFloat.random(in: 0...237)/255.0, alpha: 0.9)))
                            .frame(width: UIScreen.main.bounds.width-20,height: 530)
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                        
                        
                        ScrollView(.horizontal,showsIndicators: false,content: {
                            LazyHStack{
                                ForEach(0..<10){
                                    idx in
                                    Rectangle()
                                        .fill(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0...167)/255.0, green: CGFloat.random(in: 0...131)/255.0, blue: CGFloat.random(in: 0...89)/255.0, alpha: 0.9)))
                                        .frame(width: 200, height: 300)
                                        .clipShape(RoundedRectangle(cornerRadius: 10))
                                    
                                }
                            }
                        })
                        .padding(.leading,10)
                        .padding(.trailing,10)
                        
                        
                    }
                }
            }
            .frame(width:UIScreen.main.bounds.width)
            
        }
    }
}


#Preview {
    ScollViewBootcamp()
}

上圖看看效果:

ScrollView

簡單幾句就能實現**ScrollView**的滑動效果;非常方便。

LazyVGrid 網格佈局,先上程式碼:

import SwiftUI

struct GridViewBootcamp: View {
    
    let columns=[
        GridItem(.flexible(),spacing: 6   ,alignment: .center),
        GridItem(.flexible(),spacing: 6    ,alignment: .center),
        GridItem(.flexible(),spacing: 6  ,alignment: .center),
    ]
    
    var body: some View {
        
        ScrollView{
            LazyVGrid(columns: columns,
                      alignment: .center,
                      spacing: 6,
                      pinnedViews: [.sectionHeaders],content:
                        {
                Section(content: {}, header: {
                    Text("section header 一")
                        .font(.largeTitle)
                        .foregroundStyle(.blue)
                        .frame(width: UIScreen.main.bounds.width,height: 100,alignment: .leading)
                })
                
                ForEach(0..<41){
                    index in
                    Rectangle()
                        .fill(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0..<255)/255.0, green: CGFloat.random(in: 0..<255)/255.0, blue: CGFloat.random(in: 0...255)/255.0, alpha: 0.9)))
                        .frame(height: 50)
                }
                
                //-------
                Section {
                    
                } header: {
                    Text("section header 二")
                        .font(.largeTitle)
                        .foregroundStyle(.blue)
                        .frame(width: UIScreen.main.bounds.width,alignment: .leading)
                    
                }
                
                ForEach(0..<41){
                    index in
                    Rectangle()
                        .fill(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0..<255)/255.0, green: CGFloat.random(in: 0..<255)/255.0, blue: CGFloat.random(in: 0...255)/255.0, alpha: 0.9)))
                        .frame(height: 50)
                }
                
            })
            .padding(.leading,6)
            .padding(.trailing,6)
            .background(.gray)
        }.background(.blue)
    }
}

#Preview {
    GridViewBootcamp()
}

效果圖:

LazyVGrid

總結:LazyVGrid大家看到這個單詞有個Lazy懶載入的意思,它的內部載入item簡單來說,就是當檢視需要時,才會執行item內容渲染功能,展示UI上。也就這點注意。

SafeArea安全區域:

import SwiftUI

struct SafeAreaBootcamp: View {
    var body: some View {
        GeometryReader{
            src in
            Rectangle()
                .fill(.blue)
                .frame(maxWidth: .infinity,
                       maxHeight: .infinity)
        }
    }
}

#Preview {
    SafeAreaBootcamp()
}

效果圖:

safeArea

可以看到上下邊距存在安全區域的,如果禁用安全區域,使用 ignoresSafeArea(.all) 可以去掉。

程式碼如下:

safeArea

最後說說SwiftUI函式表達

上上程式碼:


import SwiftUI

struct ExtractFunctionsBootcamp: View {
    
    @State var bgc:Color = .red
    
    var body: some View {
        normolView
    }
    
    var normolView : some View {
        setUI()
    }
    
    func chageColor() -> Void {
        self.bgc = .red
    }
    
    func setUI()->some View {
        return ZStack{
            
            bgc
                .ignoresSafeArea(.all)
            
            VStack(spacing: 20, content: {
                
                Text("Hello, World!")
                    .font(.largeTitle)
                
                Button(action: {
                    bgc = .brown
                }, label: {
                    Text("Button")
                        .font(.largeTitle)
                        .foregroundStyle(.white)
                })
                
                Button {
                    self.chageColor()
                } label: {
                    Image(systemName: "button.horizontal.top.press")
                        .resizable()
                        .foregroundColor(.white)
                        .aspectRatio(contentMode: .fill)
                        .frame(width: 50,height: 50)
                }
            })
        }
    }
}


#Preview {
    ExtractFunctionsBootcamp()
}

其實函式表達跟我們swift語法糖一樣;func 命名;這點和swift語法糖沒什麼區別。

總結(說說我的感想)

優點:

簡潔性:Swift,SwiftUI語法簡潔,編寫程式碼變得更加容易和快速。

安全性:是一種型別安全的程式語言,可以在編譯時檢測型別錯誤,這幫助我們避免許多常見的錯誤,提高程式碼的質量和可靠性。

互操作性:它與Objective-C語言無縫互銜接,是的OC與swift程式碼混編變的更加便捷。

說完優點在說缺點

功能限制:雖然SwiftUI提供了許多常見的UI元件,但與UIKit相比,功能仍然相對有限。在某些複雜的介面需求下,可能需要使用UIKit來實現。

錯誤提示不明確:有時SwiftUI, SwiftUI的錯誤提示可能不夠明確,導致難以定位問題。

UIkit與SwiftUI缺乏無縫相容:兩者相容性不夠理想,這在業務開發中,你可能才能發現。

目前蘋果與市面大量應用也在使用Swift,SwiftUI開發應用,這們語言在應用中佔有讀也是成倍增長。

路漫漫其修遠兮,吾將上下而求索

後續更新中..........

相關文章