從用SwiftUI搭建項目說起

 

前言


 

      後續這個SwiftUI分類的文章全部都是針對SwiftUI的日常學習和理解寫的,自己利用Swift寫的第二個項目也順利上線後續的需求也不是特著急,最近正好有空就利用這段時間補一下自己對SwiftUI的理解,這個過程當中正好把整個學習過程記錄下來,方便自己查閱,也希望能給需要的同學一點點的幫助。由於自己還欠著RxSwift的帳,這次也是想著先放棄別的賬務(欠的的確挺多的)先全心全意的把這兩塊的帳給補補,希望補上這筆賬之後自己對Swift的理解也能上一個台階,對Siwft的理解自認為還是感覺欠缺的,不算是真的深入的掌握,我對SwiftUI也是在學習當中,現在能查閱的關於SwiftUI的資料很多是需要收費的,遇到問題只能想辦法努力解決,有寫的不鐘意的地方,希望多加指正!

 

                                           

      這兩張圖相信看過蘋果官方SwiftUI介紹文檔並且跟著寫了一遍程式碼的同學應該不陌生,當然我們的目的不是說這兩篇的程式碼,這個具體的可以到下面連接去查看,我自己跟著寫了一遍之後對SwiftUI也是有了一個基本的認識。我們在後面遇到的一些問題也會回到這個官方文檔進行一些驗證。

      Apple SwiftUI

 

SwiftUI


 

      在進入項目搭建先說說我自己對SwiftUI的一個基本的認知:

      SwiftUI我覺得對iOSer來說最大的是開發UI模式的優化,針對一個需求或者是一個新的項目我們基本上都是從寫UI開始的,根據設計圖再編造一些假數據來做,只是在寫的過程中它的及時效果也都是腦補!SwiftUI我覺得能改變的痛點就是這點,能讓我們實時預覽自己寫的UI效果,保持我們程式碼和介面的同步性!

      聲明式UI:關於它的理解往細了說,的確能專門寫一篇文章出來,下面這篇文章能很好的幫助理解我們現在使用的命令式和SwiftUI採用的聲明式UI之間的區別。

      從 SwiftUI 談聲明式 UI 與類型系統

      跨平台:  在最新的swiftUI 5.1中,我們創建一個MultilPlatform App有了下面這些區別:

      ·Before

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

        // Create the SwiftUI view that provides the window contents.
        let contentView = BaseTabbarView()

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }

      ·After

import SwiftUI

@main
struct MultiPlatformApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

      SwiftUI 將整個原有的蘋果平台差異部分抽象為 App 和 Scene 部分,可以看到Swift5.1之後在完全無需引入UIKit 的情況下我們就創建了一個多平台的App工程,程式碼也從原本的基於 UI/NS HostViewController 變成了基於 App的聲明式描述。這意味著我們後續在UI布局系統上可以逐漸擺脫對傳統命令式 UI 編程的依賴。達到真正的平台無關!

      下面開始我們最常見的項目場景的搭建,一點點的學習一下SwiftUI裡面的一些知識。

      實時預覽:

      這個畫布的顯示控制是在下圖標註的地方,當然當你創建一個SwiftUIView的時候它是默認創建展示的,要是不見了就在下面去找:

 

 

       畫布的程式碼控制是在你每個View 的最後面的遵循了PreviewProvider協議的結構體裡面的,就像下面我們要說的基本的Tab的預覽:

struct BaseTabbarView_Previews: PreviewProvider {
    /// 預覽視圖,你試著在這裡多添加兩個看看效果呀
    static var previews: some View {
        
        BaseTabbarView()
    }
}

 

從最常見的場景搭建開始


 

      在我們的日常開發中,標籤(TabBar)+ 導航(Na)形式的模式是隨處可見的,我們這次的目的是利用SwiftUI搭建這樣一個場景構建一個基本的應用,包括登錄和數據處理以及iOS常見控制項在SwiftUI中的一些具體的使用,這個項目會隨著學習進度慢慢的把所有的內容都基本的補齊,下面是最基本的導航+標籤的git效果。

 

 

View


 

      我自己覺得,要想從UIKit轉換到SwiftUI,需要我們最先轉變的概念就是 Controller -> View 的一個改變,在使用SiwftUI寫UI的過程中,基本上是不在需要我們向UIkit那樣去創建Controller來管理View,在SwiftUI中最常見的就是View。

      在UIKit中我們的導航、標籤都是通過控制器來管理,但是在SwiftUI中他們分別是通過NavigationView+TabView管理的,我們得在認識上有一個基本的轉變,從Controller到View的認識轉變。

      認識一下NavigationView,先看看下面的程式碼:

NavigationView{
            
      NavigationLink.init(
          destination: Text("Destination"),
          label: {
              Text("Navigate")
          })
          .navigationTitle(title)
          /// 留意下這個顯示模式 displayMode 分三種,具體的可以點進去看看
          /// inline 就是我們常見的模式
          /// .navigationBarTitle(title,displayMode: .inline)
}

      大概解析一下上面程式碼的 NavigationLink,它是用來控制View之間的跳轉的:

      destination:是跳轉的目標View,我們在做一些數據傳遞的時候一般都是在這裡說明的。

      label:對它的理解簡單點就是下個View的內容

      再認識一下TabView,下面程式碼是SwiftUI對它的基本定義和描述:

/// A view that switches between multiple child views using interactive user
/// interface elements.
///
/// To create a user interface with tabs, place views in a `TabView` and apply
/// the ``View/tabItem(_:)`` modifier to the contents of each tab. The following
/// creates a tab view with three tabs:
///
///     TabView {
///         Text("The First Tab")
///             .tabItem {
///                 Image(systemName: "1.square.fill")
///                 Text("First")
///             }
///         Text("Another Tab")
///             .tabItem {
///                 Image(systemName: "2.square.fill")
///                 Text("Second")
///             }
///         Text("The Last Tab")
///             .tabItem {
///                 Image(systemName: "3.square.fill")
///                 Text("Third")
///             }
///     }
///     .font(.headline)
///
/// Tab views only support tab items of type ``Text``, ``Image``, or an image
/// followed by text. Passing any other type of view results in a visible but
/// empty tab item.
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *)
public struct TabView<SelectionValue, Content> : View where SelectionValue : Hashable, Content : View {

    /// Creates an instance that selects from content associated with
    /// `Selection` values.
    public init(selection: Binding<SelectionValue>?, @ViewBuilder content: () -> Content)

    /// The content and behavior of the view.
    public var body: some View { get }

    /// 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 `body` property.
    public typealias Body = some View
}

 

      關於這個TabView在定義的上面蘋果是給出了一個使用的基本的示例的,要和我們項目中經常使用的模式要綁定在一起的的話就是結合他的初始化方法綁定一個@State變數使用的,具體的我們會在後面的程式碼中說的,關於這個@State我在項目Demo中有具體的解釋,包括像@bind類型或者是@EnvironmentObject這些關鍵字我們肯定是得需要學習的,就像我們從OC轉到Swift一樣。

      

簡單看看Na+Tb的程式碼


 

       從SceneDelegate開始, 根控制器就是 UIHostingController,我們需要做的第一步就是設置它的根視圖 rootView

// Create the SwiftUI view that provides the window contents.
let contentView = BaseTabbarView()

// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
            
      let window = UIWindow(windowScene: windowScene)
      window.rootViewController = UIHostingController(rootView: contentView)
      self.window = window
      window.makeKeyAndVisible()
}

       接下來是TabView的程式碼,需要注意的是我們點擊item的時候視圖切換的綁定狀態,基本上在程式碼注釋中我說的比較清楚了,應該能理解的。

import SwiftUI

struct BaseTabbarView: View {
    
    /// 理解State這個屬性 //www.cnblogs.com/xiaoniuzai/p/11417123.html
    /*
     通過使用 @State 修飾器我們可以關聯出 View 的狀態. SwiftUI 將會把使用過 @State 修飾器的屬性存儲到一個特殊的記憶體區域,並且這個區域和 View struct 是隔離的. 當 @State 裝飾過的屬性發生了變化,SwiftUI 會根據新的屬性值重新創建視圖
     */
    @State private var selectedTab = 0
    
    var body: some View {
        
        /// public init(selection: Binding<SelectionValue>?, @ViewBuilder content: () -> Content)
        TabView(selection: $selectedTab){
            
            HomeView(title: "首頁")
                .tabItem {
                    Image(selectedTab == 0 ? "icon_home_selected" : "icon_home")
                    Text("首頁")
                }
                .onTapGesture {
                    selectedTab = 0
                }
                .tag(0)
            
            ServiceView(title: "周邊")
                .tabItem {
                    Image(selectedTab == 1 ? "icon_around_selected" : "icon_around")
                    Text("周邊")
                }
                .onTapGesture {
                    selectedTab = 1
                }
                .tag(1)
            
            AroundView(title: "服務")
                .tabItem {
                    Image(selectedTab == 2 ? "icon_service_selected" : "icon_service")
                    Text("服務")
                }
                .onTapGesture {
                    selectedTab = 2
                }
                .tag(2)
            
            MineView(title: "我的").environmentObject(NavigationAction())
                .tabItem {
                    Image(selectedTab == 3 ? "icon_mine_selected" : "icon_mine")
                    Text("我的")
                }
                .onTapGesture {
                    selectedTab = 3
                }
                .tag(3)
        /// 這個著重顏色設置可以設置tabbaritem字體的顏色
        }.accentColor(.blue)
    }
}

struct BaseTabbarView_Previews: PreviewProvider {
    /// 預覽視圖,你試著在這裡多添加兩個看看效果呀
    static var previews: some View {
        
        BaseTabbarView()
    }
}

 

      在上面的程式碼中,點擊的切換我們是通過View的onTapGesture方法通過改變selectedTab 來進行控制的,然後item具體是要顯示那種風格的圖片也是通過selectedTab經過三目運算符控制,具體得我們這裡不多解釋了廢話了,看程式碼我相信都能理解。

      下面的參考文章相信能幫助我們更好的理解一下,SwiftUI!

 

參考文章:

      Apple SwiftUI

      從 SwiftUI 談聲明式 UI 與類型系統

      如何評價 SwiftUI?

      項目地址

 

Tags: