原創: 彭權華
首發:「知曉雲」公衆號 - 讓你的小程序開發快人一步
SwiftUI 是蘋果最新推出的 UI 開發工具,其具有以下特點:採用聲明式語法,易於閱讀、代碼更少;跨所有蘋果平臺,共用一套
API;自動支持動態類型、暗黑模式、本地化等。採用 SwiftUI 將大大提高 UI 界面開發效率。
大家好,我是小彭。蘋果近期推出了一個全新的 SwiftUI 框架,可以極大地提高 iOS 上 UI 界面的開發效率。今天小彭就用 SwiftUI 來實現一個新聞資訊 app,看看能有多快。
【關注「知曉雲」公衆號,回覆關鍵字「SwiftUI」獲取完整代碼】
受篇幅所限,我們將通過上下兩篇文章爲大家介紹如何實現一個完整的新聞資訊 app,本篇主要內容有:
SwiftUI 的基礎知識:預覽、View 協議、修飾器、@State 特性等。
使用 NavigationView、NavigationLink、List、Text、Image 等基本視圖和 HStack、VStack 常用的佈局方式創建資訊列表頁、資訊詳情頁並顯示從 daliy.plist 文件讀取的新聞信息。
開始
在 Xcode 11,創建一個新項目,選擇 iOS->Single View APP,命名爲 Daily,並選中 Use SwiftUI。
ScenDelegate
SwiftUI 項目新增了一個 ScenDelegate.swift 文件,AppDelegate 和 ScenDelegate 共同用於管理 app 的生命週期。ScenDelegate 的 scene(_:willConnectTo:options:) 進行 UI 配置:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = ContentView()
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
預覽(Preview)
打開 ContentView.swift 文件,右上角有一個 Resume 按鈕,如果沒有看見 Resume 按鈕,選擇 Editor > Canvas。
點擊 Resume 對 ContentView 視圖進行預覽
要支持預覽視圖,需要實現 PreviewProvider 協議,並在協議屬性內返回該視圖實例,比如爲 ContentView 提供預覽:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
以往寫一個頁面,如果這個頁面藏得很深,那麼你運行後進入到這個頁面需要耗費很長時間。現在不需要運行,通過預覽可以馬上看到頁面效果,節省了頁面開發時間。
注意:必須 macOS 10.15 以上系統才支持預覽功能!
View 協議和修飾器
ContentView 實現了 View 協議。所有視圖都需要實現 View 協議,在協議屬性 body 內提供視圖內容和佈局。
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
Text 也是一個 View。注意:自定義視圖的 body 最多創建 10 個子視圖。
通過修飾器(modifier)來配置視圖,比如將 Text 的文字設置爲紅色,字體爲 system,大小爲 16,那麼可以通過 foregroundColor、font 修飾器來配置:
Text("Hello World")
.foregroundColor(.red)
.font(.system(size: 16))
每個修飾器都會返回新的視圖 View,通過多個修飾器形成一個修飾鏈來完成對適配的配置。需要注意的是,修飾器的順序不同可能產生不一樣的結果。
數據準備
每一條新聞資訊包含標題、作者、發佈日期、詳細內容、圖片等信息。創建一個 swift 文件,命名爲 News.swift。在該文件內創建一個新聞模型 News:
struct News: Identifiable {
var id: String
var author: String
var title: String
var date: Date
var content: String
var thumbnail: String
}
爲了簡單起見,預定義了 2 條新聞資訊信息。fetchDaliy() 方法獲取新聞信息並返回 News 數組:
func fetchDaliy() -> [News] {
let newsDicts = [
["id": "0", "title": "60 天打印一枚火箭", "author": "ifanr", "content": "今年 4 月,SpaceX 重型獵鷹完成首次商業發射", "date": "10-14"],
["id": "1", "title": "當你在看電視的時候,電視也在看你", "author": "ifanr", "content": "智能電視廣告的問題着實讓人心煩。", "date": "10-15"]]
var newsList: [News] = []
for newsDict in newsDicts {
let news = News(id: newsDict["id"]!, title: newsDict["title"]!, author: newsDict["author"]!, date: newsDict["date"]!, content: newsDict["content"]!, thumbnail: newsDict["thumbnail"]!)
newsList.append(news)
}
return newsList
}
UI 構建
App 的主要內容有:新聞列表頁、新聞詳情頁。我們將所有新聞資訊展示在一個列表上,每一行代表一條新聞,點擊一行進入到資訊詳情頁。相應地,需要三個頁面:列表頁、列表項、詳情頁。新建三個 SwiftUI 文件,分別命名爲 NewsListView.swift、NewsListRow.swift、NewsDetailView.swift 。NewListView 表示列表頁,NewsListRow 表示列表項,NewsDetailView 表示詳情頁。
頁面導航
在列表頁點擊一條新聞,將跳轉到詳情頁,同時詳情頁也能夠返回到列表頁。在 SwiftUI 中,使用 NavigationView 和 NavigationLink 來實現頁面之間的導航跳轉。將列表頁 NewsListView 嵌入到 NavigationView 中,SwiftUI 會在 NewsListView 頂部增加一個導航欄 NavigationBar。打開 ContentView.swift 文件,增加以下代碼:
struct ContentView: View {
var body: some View {
NavigationView {
NewsListView()
}.navigationViewStyle(StackNavigationViewStyle())
}
}
navigationViewStyle 指定導航類型,若未指定,在 iPhone 和 iPad 默認的導航類型不一樣。
新聞列表頁
在 NewsListView 中,我們需要獲取所有新聞資訊數據,並展示在一個列表上。
通過 List 聲明一個列表,並指定數據源和每一行的內容。
struct NewsListView: View {
var newsList: [News] = fetchDaliy()
var body: some View {
List(newsList) { (news) in
NavigationLink(destination: NewsDetailView()) {
NewsListRow()
}
}
}
}
NavigationLink 通過指定目標頁面,實現頁面之間的跳轉。
另外需要注意的是,List 的初始化方法指定了數據源,數據源的元素需要實現 Identifiable,因此我們在 News 類型實現了 Identifiable。
新聞列表項
每一個列表項顯示一條新聞,內容包含圖片、標題、發佈日期。佈局如下圖:
在 SwiftUI 中,使用 HStack 和 VStack 來組合不同的使用的視圖。HStack 表示橫向關係,VStack 表示縱向關係。標題和日期是縱向關係,而標題和日期組合起來和圖片是橫向關係。NewListRow 的實現如下面代碼:
struct NewsListRow: View {
var news: News
var body: some View {
HStack {
WebImageView(imageUrl: news.thumbnail)
.frame(width: 80, height: 100)
VStack(alignment: .leading) {
Text(news.title)
.font(.headline)
.padding(.top, 10)
Spacer()
Text(news.date)
.foregroundColor(.secondary)
.padding(.bottom, 10)
}
.padding(.leading, 10)
}
}
}
我們需要實現 WebImageView 獲取網絡圖片並顯示,圖片未下載時顯示一個佔位圖片,當圖片下載後將顯示該網絡圖片:
struct WebImageView: View {
@State private var uiImage: UIImage? = nil
let placeholderImage = UIImage(named: "mine_ifanr")
var imageUrl: String
var body: some View {
Image(uiImage: self.uiImage ?? placeholderImage!)
.resizable()
.onAppear(perform: downloadWebImage)
}
func downloadWebImage() {
guard let url = URL(string: imageUrl) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data, let image = UIImage(data: data) {
self.uiImage = image
} else {
print("error: \(String(describing: error))")
}
}.resume()
}
}
Image 是 SwiftUI 的圖片控件,uiImage 爲 nil 時,Image 視圖顯示 placeholderImage 佔位圖。當 WebImageView 出現時(onAppear),將會根據 imageUrl 下載圖片,下載完成後將 image 賦值給 uiImage,此時 SwiftUI 會自動更新 Image 的圖片。Image 能夠自動更新,是因爲將 uiImage 聲明瞭 @State 特性。將 View 的存儲屬性聲明爲 @State 後,每當該屬性的值發生變化時,SwiftUI 都會重新繪製 body 的內容。
新聞詳情頁
在新聞詳情頁,我們需要顯示標題、作者、發佈日期、詳細內容。佈局如圖:
代碼如下所示
struct NewsDetailView: View {
var news: News
var body: some View {
VStack {
Text(news.title)
.font(.system(size: 24))
HStack {
Text("作者: \(news.author)")
Text(news.date)
}.padding(.top, 2)
Text(news.content)
.padding(.top, 5)
Spacer()
}.navigationBarTitle("詳情", displayMode: .inline)
.padding(EdgeInsets(top: 10, leading: 10, bottom: 0, trailing: 10))
}
}
通過 navigationBarTitle 來設置詳情的導航標題,以及展示模式。padding 設置視圖間的間隔。
小結
以上雖然解決了 UI 構建的問題,但實際應用中,數據都是動態變化的,所以我們需要與後端服務器進行交互。下一篇文章小彭將繼續爲大家介紹 SwiftUI 的 ObservableObject、@ObservedObject、@Published 等特性。以及如何從服務器獲取數據,實現新聞諮詢的動態更新。
【關注「知曉雲」公衆號,點擊菜單欄「知曉雲」-「知曉課堂」,獲取更多開發教程。】