第一篇构建一个Landmarks详情页

366 阅读11分钟

SwiftUI基础知识

创建和组合视图

本教程通过构建一个Landmarks项目来引导学习swift UI。Landmarks翻译过来是地标的意思,发现一个喜欢的地方,进行共享的应用程序。首先从构建一个地标详情页开始。

为了布局视图,Landmarks使用stacks将图像和文本视图组件进行组合展示。通过导包含一个标准的MapKit组件,将地图添加到视图中。当您完善视图的设计时,Xcode提供实时反馈,以便您可以看到这些更改如何转化为代码。

下载项目文件Project files以开始构建此项目,并按照以下步骤操作。

第1节 创建一个新项目并探索画布

使用Xcode创建一个使用SwiftUI项目。探索画布、预览和SwiftUI模板代码。

要在Xcode中预览和与画布上的视图进行交互,并使用教程中描述的所有最新功能,请确保您的Mac运行macOS Monterey或更高版本。

第1步

打开Xcode,在Xcode的启动窗口中单击“创建新的Xcode项目”,或选择文件>新建>项目。
create project

第2步

在模板选择器中,选择iOS作为平台,选择app模板,然后单击下一步。 select app template

第3步

输入“Landmarks”作为项目名称,为界面选择“SwiftUI”,为语言选择“Swift”,然后单击下一步。在Mac上选择一个位置,保存Landmarks项目。 save

第4步

在项目导航器中,选择Landmarks.swift

使用SwiftUI的应用程序,应用程序的生命周期有一个符合App协议的结构。结构的body属性返回一个或多个场景,这些场景反过来提供内容以供显示。@main属性标识应用程序的入口点。

//LandmarksApp.swift

import SwiftUI
@mainstruct LandmarksApp: App {    
         var body: some Scene {        
             WindowGroup {           
                 ContentView()       
             }    
        }
}

第5步

在项目导航器中,选择Content.swift

默认情况下,SwiftUI视图文件会声明两种结构。第一个结构符合View协议,并描述了视图的内容和布局。第二个结构声明该视图的预览。

//ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, World!")
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

第6步

在画布中,单击“resume”以显示预览。
提示
如果画布不可见,请选择editor>canvas来显示它。

第7步

在body属性内,更改“Hello, world!”给自己一个问候。
当您更改视图body属性中的代码时,预览会更新以反映您的更改。

第2节 自定义文本视图

您可以通过更改代码或使用检查器来发现可用内容并帮助您编写代码来自定义视图的显示。

当您构建Landmarks应用程序时,您可以使用任何编辑器组合:源编辑器、画布或检查器。无论您使用哪种工具,您的代码都会保持更新。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello SwiftUI!")
            .font(.title)
            .foregroundColor(.green)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

第1步

接下来,您将使用检查器自定义文本视图。
在预览中,点击Selectable,按住 Command 键单击问候语以调出结构化编辑弹出窗口,然后选择“Show SwiftUI Inspector”。
弹出窗口显示您可以自定义的不同属性,具体取决于您检查的视图类型。

第2步

使用检查器将文本更改为“Turtle Rock”,这是您将在应用程序中显示的第一个地标的名称。

第3步

将字体修饰符更改为“title”。
这会将系统字体应用于文本,以便它正确响应用户的首选字体大小和设置。
要自定义SwiftUI视图,您可以调用称为Modifier的方法。修饰符包装视图以更改其显示或其他属性。每个修饰符返回一个新的视图,因此链接多个修饰符,垂直堆叠是很常见的。

import SwiftUI
struct ContentView: View {
    var body: some View {
        Text("Turtle Rock")
            .font(.title)
            .foregroundColor(.green)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

第5步

您的代码始终是视图的真相来源。当您使用检查器更改或删除修饰符时,Xcode会立即更新您的代码以匹配。
这一次,通过Command 单击代码编辑器中的Text声明来打开检查器,然后从弹出窗口中选择“Show SwiftUI Inspector”。单击颜色弹出式菜单,然后选择继承,以再次将文本颜色更改为黑色。

第6步

请注意,Xcode会自动更新您的代码以反映更改,删除foregroundColor(.green)修饰符。

第3节 使用堆栈组合视图

除了您在上一节中创建的标题视图外,您还会添加文本视图以包含有关地标的详细信息,例如公园的名称和所在状态。

创建SwiftUI视图时,您可以在视图的body属性中描述其内容、布局和行为;但是,正文属性仅返回单个视图。您可以在stack中组合和嵌入多个视图,通过水平、垂直或前后将视图组合在一起。

在本节中,您将使用VStack将标题放在包含公园详细信息的HStack上方。
您可以使用Xcode的结构化编辑支持将视图嵌入容器视图,打开检查器,或帮助进行其他有用的更改。

第1步

按住 Command 键单击文本视图的初始化器以显示结构化编辑弹出窗口,然后选择“Embed in VStack”。

第2步

接下来,您将通过从库中拖动Text视图将文本视图添加到堆栈中。
通过单击Xcode窗口右上角的加号按钮(+)打开库,然后将Text视图拖动到代码中“Turtle Rock”文本视图下方的位置。

第3步

Text视图的占位符文本替换为“Joshua Tree National Park”。

第4步

将位置的字体设置为subheadline

第5步

编辑VStack初始化器,使其内部视图左对齐。 默认情况下,堆栈将其内容沿着轴线居中,并提供适合上下文的间距。

import SwiftUI
struct ContentView: View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("Turtle Rock")
                .font(.title)
                .foregroundColor(.black)
            Text("Joshua Tree National Park")
                .font(.subheadline)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

接下来,您将在位置右侧添加另一个文本视图,这是公园的状态。

第6步

在画布中,按住 Command 键单击“Joshua Tree National Park”,然后选择“Embed in HStack”。

第7步

在位置后添加新的文本视图,将占位符文本更改为公园的所属州,然后将其字体设置为subheadline

第8步

为了让公园和州横向充满整个屏幕,这里加了一个Spacer
Spacer使其包含视图使用其父视图的所有空间,而不是仅由其内容定义其大小。

第9步

最后,使用padding()修饰符方法给地标的名称和细节更多的空间。

import SwiftUI
struct ContentView: View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("Turtle Rock")
                .font(.title)
                .foregroundColor(.black)
            HStack {
                Text("Joshua Tree National Park")
                    .font(.subheadline)
                Spacer()
                Text("California")
                    .font(.subheadline)
            }
        }
        .padding(.leading, 10)
        .padding(.trailing, 10)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

效果

第4节 创建自定义图像视图

在名称和位置视图都设置好后,下一步是为地标添加图像。

您将创建一个自定义视图,将蒙版、边框和阴影应用于图像,而不是在此文件中添加更多代码。

image.png

第1步

首先将图像添加到项目的资产目录中。
在项目文件的资源文件夹中找到turtlerock@2x.jpg;将其拖动到资产目录的编辑器中。Xcode为图像创建一个新的图像集。

接下来,您将为您的自定义图像视图创建一个新的SwiftUI视图。

第2步

选择文件>新建>文件以再次打开模板选择器。在用户界面部分,选择“SwiftUI View”,然后单击下一步。将文件命名为Circle.swift,然后单击创建。

第3步

使用Image(_:)初始化器将文本视图替换为Turtle Rock的图像,并将要显示的图像的名称传递给它。

import SwiftUI

struct CircleImage: View {
    var body: some View {
        Image("turtlerock")
    }
}

struct CircleImage_Previews: PreviewProvider {
    static var previews: some View {
        CircleImage()
    }
}

第4步

添加对clipShape(Circle())的调用,将圆形剪切形状应用于图像。

Circle类型是一种形状,您可以将其用作遮罩,或者理解成给视图画一个圆并进行填充。

第5步

创建另一个带有灰色笔画的圆圈,然后将其添加为覆盖层,以使图像具有边框。

第6步

接下来,添加一个半径为7点的阴影。

第7步

将边框颜色切换为白色。 这就完成了图像视图。

import SwiftUI

struct CircleImage: View {
    var body: some View {
        Image("turtlerock")
            .clipShape(Circle())
            .overlay {
                Circle().stroke(.gray, lineWidth: 4)
            }
            .shadow(radius: 7)
    }
}

struct CircleImage_Previews: PreviewProvider {
    static var previews: some View {
        CircleImage()
    }
}

效果

第5节 使用来自其他框架的SwiftUI视图

接下来,您将创建一个以给定坐标为中心的地图。您可以使用MapKit中的Map视图来渲染地图。

第1步

选择文件>新建>文件,选择iOS作为平台,选择“SwiftUI View”模板,然后单击下一步。命名新文件MapView.swift,然后单击创建。

第2步

Map添加import语句。

当您在同一文件中导入SwiftUI和某些其他框架时,您可以访问该框架提供的SwiftUI特定功能。

第3步

创建一个私有状态变量,保存地图的区域信息。

您使用@State属性为应用程序中的数据建立真实来源,您可以从多个视图中进行修改。SwiftUI管理底层存储,并自动更新依赖于值的视图。

第4步

将默认Text视图替换为与区域绑定的Map视图。

通过在状态变量前加上$,您可以传递一个绑定,这就像对基础值的引用。当用户与地图交互时,地图会更新区域值,以匹配地图中当前在用户界面中可见的部分。

import SwiftUI
import MapKit

struct MapView: View {
    
    @State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868), span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
    )
    
    
    var body: some View {
        Map(coordinateRegion: $region)
    }
}

struct Map_Previews: PreviewProvider {
    static var previews: some View {
        MapView()
    }
}

第6节 编写详情页面

您现在拥有了所需的所有组件——名称和地点、圆形图像和位置地图。

使用您到目前为止使用的工具,结合您的自定义视图,为地标详情页创建最终设计。

第1步

在项目导航器中,选择Content.swift文件。

第2步

将包含三个文本视图的VStack嵌入到另一个VStack中。

第3步

将自定义Map添加到堆栈顶部。设置Map的大小frame(width:height:)

当您仅指定height参数时,视图会自动调整其内容的宽度。在这种情况下,Map会扩展以填充可用空间。

第4步

单击实时预览以在组成的视图中查看渲染的地图。
您可以在显示实时预览时继续编辑视图。

第5步

Circle视图添加到堆栈中。

第6步

要将图像视图分层到地图视图的顶部,请给图像垂直偏移-130点,并从视图底部填充-130点。
这些调整通过向上移动图像为文本腾出空间。

第7步

在外部VStack的底部添加一个Spacer,将内容推送到屏幕顶部。

第8步

要允许地图内容扩展到屏幕顶部边缘,请将 ignoresArea(edges: .top)修饰符添加到地图视图中。

第9步

为地标添加分隔符Divider和一些额外的描述性文本。

第10步

最后,将副标题字体修饰符从每个Text视图移动到包含它们的HStack,并将次要颜色应用于副标题文本。

当您将修饰符应用于堆栈等布局视图时,SwiftUI会将修饰符应用于组中包含的所有元素。

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            MapView()
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)
            CircleImage()
                .offset(y: -130)
                .padding(.bottom, -130)
            VStack(alignment: .leading) {
                Text("Turtle Rock")
                    .font(.title)
                    .foregroundColor(.black)
                
                HStack {
                    Text("Joshua Tree National Park")
                    Spacer()
                    Text("California")
                        
                }
                .font(.subheadline)
                .foregroundColor(.secondary)
                
                Divider()
                
                Text("About Turtle Rock")
                    .font(.title2)
                Text("Descriptive text goes here.")
            }
            .padding(.leading, 10)
            .padding(.trailing, 10)
            
            Spacer()
        }
        
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

效果

总结

现在的功能比较少,代码看起来还行,如果碰到需求比较复杂的情况,对于这种声明式的语言框架就比较痛苦了,因为嵌套地狱实在太恶心了,目前也没什么好的方案可以解决,只能在写代码的时候尽量将嵌套的东西抽出来,让代码逻辑清晰一些了。

到这里,从创建项目到实现Landmarks详情页的UI功能就算完成了,后面还有列表页、个人主页等其它模块,以及用到的一些数据,如何采用响应式编程实现逻辑处理,这些我们后面接着做。好了本篇文章到这里就结束了,后面内容我会陆续更新,有需要的同学可以到主页查看