天天看點

SwiftUI 初體驗 2 清單使用 / 裝置預覽設定

首先要建立List裡面嵌套的RowView,建立SwiftUI類命名為RowView。

struct RowView: View {
    
    var species: Species
    
    var body: some View {
        HStack {
            species.image()
                .resizable()
                .aspectRatio(1, contentMode: .fit)
                .frame(width: 50, height: 50, alignment: .center)
                .clipShape(Circle())
                .padding(EdgeInsets(top: 8, leading: 15, bottom: 8, trailing: 8))
            Text(species.description)
            Spacer()
        }
    }
    
}
           

結構體中添加屬性species物種,description描述。在body中添加Image和Text組合的Stack。為了預覽效果我們可以在PreviewProvider中修改預覽樣式,其中previewLayout隻是在預覽時設定相關屬性。

struct RowView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            RowView(species: "lion", description: "The lion is a ferocious beast")
            RowView(species: "horse", description: "The horse run fast")
        }
        .previewLayout(.fixed(width: 400, height: 80))
    }
}
           

效果如下:

SwiftUI 初體驗 2 清單使用 / 裝置預覽設定

其中使用Group可以展示多個預覽,previewLayout可以跟在單個項目中,也可以跟在Group後面。

為了友善先在Xcode中建立一個檔案,重命名為AnimalsData.json,裡面模拟一份json資料。為了在Bundle.main中加載這個檔案,必須在target的BuildPhases的CopyBundleResources中添加引用。

[
    {
        "id": 1001,
        "name": "cat",
        "imageName": "cat",
        "description": "Cats can rule humans"
    },
    {
        "id": 1002,
        "name": "cat",
        "imageName": "dog",
        "description": "Dog is a friend of mankind"
    },
    {
        "id": 1003,
        "name": "cat",
        "imageName": "elephant",
        "description": "The size of elephant is very huge"
    },
    {
        "id": 1004,
        "name": "cat",
        "imageName": "horse",
        "description": "Horses run fast"
    },
    {
        "id": 1005,
        "name": "cat",
        "imageName": "lerpard",
        "description": "Lerpard is a beast that eats meat"
    },
    {
        "id": 1006,
        "name": "cat",
        "imageName": "lion",
        "description": "The lion is a ferocious beast"
    },
    {
        "id": 1007,
        "name": "cat",
        "imageName": "mouse",
        "description": "The mouse is very small"
    },
    {
        "id": 1008,
        "name": "cat",
        "imageName": "rabbit",
        "description": "Rabbits is the food that beast like"
    },
    {
        "id": 1009,
        "name": "cat",
        "imageName": "tiger",
        "description": "Tiger is a carnivorous animal"
    },
    {
        "id": 1010,
        "name": "cat",
        "imageName": "wolf",
        "description": "wolves live together"
    }
]

           

建立一個Data.swift檔案,作為資料源讀取的工具類。

//
//  Data.swift
//  SwiftUIDemo
//
//  Created by Eugene on 2019/9/28.
//  Copyright © 2019 Eugene. All rights reserved.
//

import SwiftUI
import UIKit

let speciesData: [Species] = load("AnimalsData.json")

func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
    
    let data: Data
    
    guard let file = Bundle.main.url(forResource: filename, withExtension: nil) else {
        fatalError("Couldn't find \(filename) in main bundle.")
    }
     
    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Could't load \(filename) from main bundle: \n \(error)")
    }
    
    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Coludn't parse \(filename) as \(T.self):\n\(error)")
    }
    
}

final class ImageStore {
    
    typealias _ImageDictionary = [String: CGImage]
    fileprivate var images: _ImageDictionary = [:]
    fileprivate static var scale = 2
    static var shared = ImageStore()
    
    static func loadImage(name: String) -> CGImage {
        
        guard
            let url = Bundle.main.url(forResource: name, withExtension: nil),
            let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
            let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)
        else {
            fatalError("Couldn't load iamge \(name) from main bundle")
        }
        
        return image
        
    }
    
    fileprivate func _guaranteeImage(name: String) -> _ImageDictionary.Index {
        
        if let index = images.index(forKey: name) {
            return index
        }
        
        images[name] = ImageStore.loadImage(name: name)
        
        return images.index(forKey: name)!
        
    }
    
    func image(name: String) -> Image {
        
        let index = _guaranteeImage(name: name)
        
        return Image(images.values[index], scale: CGFloat(ImageStore.scale), label: Text(verbatim: name))
        
    }
    
}

           

這個工具類可以提供我們需要的資料源,在demo裡當是模拟後端傳回的資料了。當然實體類的decode和encode有它的規則,需要遵循協定。下面把實體類也貼上來。

import SwiftUI

struct Species: Hashable, Codable, Identifiable {
    
    var id: Int
    var name: String
    var description: String
    var imageName: String
    
}

extension Species {
    
    func image() -> Image {
        let picture = Image.init(imageName)
        return picture
    }
    
}

           

到這裡準備工作就差不多了。回到AnimalList頁面,編輯代碼:

import SwiftUI

struct AnimalList: View {
    var body: some View {
        NavigationView {
            List(speciesData) { species in
                NavigationLink(destination: AnimalDetails()) {
                    RowView(species: species)
                }
            }
            .navigationBarTitle(Text("Animals"))
        }
    }
}

struct AnimalList_Previews: PreviewProvider {
    static var previews: some View {
        AnimalList()
    }
}

           

首先List(speciesData) { species in …} 是周遊資料源speciesData以展示清單,(speciesData的讀取測試可以建立一個TestCase嘗試把它print出來,裡面的fatalError可以幫助我們準确跟蹤到哪一步出了問題。在我編輯上面的json檔案時就多次出現格式錯誤,通過fatalError可以快速定位。建議大家養成良好的使用測試用例的習慣,有助于加強我們的代碼健壯性。)通過NavigationLink(destination: …) { cell的view} 我們可以綁定click事件和響應。這個Link包裹住的cell有自動生成的執行個體方法,(傳進去的參數就是我們上面的實體類Species,是以Cell的編寫最好有良好的格式習慣)我們把周遊的項目傳遞進去每一個cell,就可以準确的展示json的内容。

其中配合NavigationLink使用之前必須用NavigationView嵌套頁面,表示目前控制器以NavigationController調配控制。而NavigationView裡面的項目,可以通過navigationBarTitle來标注其标題。

預覽效果如下:

SwiftUI 初體驗 2 清單使用 / 裝置預覽設定

在navigation進行push的時候,資料的傳遞跟上面也是類似的,先寫一個簡單的details頁面

import SwiftUI

struct AnimalDetails: View {
    
    var species: Species
    
    var body: some View {
        VStack {
            HStack {
                species.image()
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .clipShape(Rectangle())
                    .clipped()
            }
            VStack {
                Text(species.description)
                    .fontWeight(.medium)
                    .font(.system(size: 24))
            }
            Spacer()
        }
    }
    
}

struct AnimalDetails_Previews: PreviewProvider {
    static var previews: some View {
        AnimalDetails(species: speciesData[0])
    }
}

           

可以看到preview provider中的初始化方法也跟着改變了,那麼list頁面的代碼稍作修改:

struct AnimalList: View {
    var body: some View {
        NavigationView {
            List(speciesData) { species in
                NavigationLink(destination: AnimalDetails(species: species)) {
                    RowView(species: species)
                }
            }
            .navigationBarTitle(Text("Animals"))
        }
    }
}
           

在建立AnimalDetails執行個體的時候就會把周遊的entity傳遞過去。live preview就可以進行互動看到效果:

SwiftUI 初體驗 2 清單使用 / 裝置預覽設定

順帶一提的是,preview中可以同時對使用的device和名稱進行指定:

struct AnimalList_Previews: PreviewProvider {
    static var previews: some View {
        ForEach(["iPhone 8", "iPhone 11 Max"], id: \.self) { deviceName in
            AnimalList()
                .previewDevice(PreviewDevice(rawValue: deviceName))
                .previewDisplayName(deviceName)
        }
    }
}

           
SwiftUI 初體驗 2 清單使用 / 裝置預覽設定

這似乎就是Apple這個大版本的其中一個很不錯的賣點,為開發者跨平台統一開發和管理調試項目提供了極大的便利,該會是一定程度上繼續推動apple已經相當優秀的軟體生态繼續發展。

繼續閱讀