【干货】仅需3 分钟,使用 SwiftUI 开发一个新闻资讯 app(上)

原创: 彭权华
首发:「知晓云」公众号 - 让你的小程序开发快人一步
在这里插入图片描述

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 等特性。以及如何从服务器获取数据,实现新闻咨询的动态更新。

【关注「知晓云」公众号,点击菜单栏「知晓云」-「知晓课堂」,获取更多开发教程。】
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章