原创: 彭权华
首发:「知晓云」公众号 - 小程序开发快人一步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 等特性。以及如何从服务器获取数据,实现新闻咨询的动态更新。
【关注「知晓云」公众号,点击菜单栏「知晓云」-「知晓课堂」,获取更多开发教程。】