原創: 彭權華
首發:「知曉雲」公衆號 - 小程序開發快人一步git
SwiftUI 是蘋果最新推出的 UI 開發工具,其具備如下特色:採用聲明式語法,易於閱讀、代碼更少;跨全部蘋果平臺,共用一套 API;自動支持動態類型、暗黑模式、本地化等。採用 SwiftUI 將大大提升 UI 界面開發效率。github
你們好,我是小彭。蘋果近期推出了一個全新的 SwiftUI 框架,能夠極大地提升 iOS 上 UI 界面的開發效率。今天小彭就用 SwiftUI 來實現一個新聞資訊 app,看看能有多快。小程序
【關注「知曉雲」公衆號,回覆關鍵字「SwiftUI」獲取完整代碼】swift
受篇幅所限,咱們將經過上下兩篇文章爲你們介紹如何實現一個完整的新聞資訊 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 配置:session
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。app
點擊 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 等特性。以及如何從服務器獲取數據,實現新聞諮詢的動態更新。
以上雖然解決了 UI 構建的問題,但實際應用中,數據都是動態變化的,因此咱們須要與後端服務器進行交互。下一篇文章小彭將繼續爲你們介紹 SwiftUI 的 ObservableObject、@ObservedObject、@Published 等特性。以及如何從服務器獲取數據,實現新聞諮詢的動態更新。
【關注「知曉雲」公衆號,點擊菜單欄「知曉雲」-「知曉課堂」,獲取更多開發教程。】