首先要建立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))
}
}
效果如下:
其中使用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來标注其标題。
預覽效果如下:
在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就可以進行互動看到效果:
順帶一提的是,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)
}
}
}
這似乎就是Apple這個大版本的其中一個很不錯的賣點,為開發者跨平台統一開發和管理調試項目提供了極大的便利,該會是一定程度上繼續推動apple已經相當優秀的軟體生态繼續發展。