首先要创建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已经相当优秀的软件生态继续发展。