

SwiftUI 系列教程(2)—— 与 UIKit 结合的自定义视图
source link: http://davidleee.com/2019/07/08/swiftui-serial-tutorial-2/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在上一篇文章中,我们了解了 SwiftUI 的 Text
组件,并通过 Stack
系列的组件对内容进行了一些简单的布局。在这篇文章里,我们会认识一个全新的图片组件,并且会尝试利用这两篇文章的知识,结合 MapKit 框架,来实现一个简单的地点详情界面。
写完第一篇文章之后,本职的开发任务突然进入了紧张的预发布阶段,搞得早就写好的第二篇文章拖了这么久才完成润色和发布,看来“全网最早”要丢了…
自定义图片视图
首先把一张地标图片放到 Assets.xcassets 里去,我在百度找了张广州塔的照片:
然后,我们要为新的图片视图创建一个新的类,就放在上一篇文章的 ContentView.swift 旁边好了。新建文件的时候,选择 SwiftUI View:
取个名字叫 CircleImage,因为我们将要在这里把广州塔裁剪成一个圆。新建的代码内容跟上一章看到的一样,我们把 Text
改成 Image
,然后把图片的名字传进去,直接就可以通过预览在画布上看到我们的图片了:
接下来我们在代码里给它加上一个圆形的裁剪。原来的做法有很多了,最快速的做法应该是操作 clipsToBounds
和 cornerRadius
,给图片加上长度等于一半宽高的圆角,这还得要求图片是正方形的才能达到满意的显示效果。
而在 SwiftUI 里,这就是一句话的事情:
.clipShape()
给图片加了个裁剪的形状,其中 Circle
类型是一个用来当作遮罩的图形,你也可以给它加上填充色或者描边来单独使用,类似于以往通过 CALayer
去实现的效果。
但这也太大了,我们的屏幕装不下,我们可以再加两行代码,把图片缩放到一个合适的大小:
讲道理,这里设置的宽高应该是一样的,毕竟是个圆嘛…但是我懒得重新截图了,各位童鞋自己调整一下数值就可以了
为了让图片本身在不同背景下都能凸显出来,我们再给它加个描边,这要通过 overlay()
方法去实现;也许再加个阴影吧,用到的是 shadow()
方法;最后出来的效果是这样的:
是不是醒目多啦?
每当做完一个新视图,我就想对比一下用老方法实现同样的效果有什么不同…
在 SwiftUI 里使用 UIKit
不知道大家发现了没有,我们在 SwiftUI 里用到的视图全部都是 struct
,这意味着它们跟我们原本熟悉的 UIKit 是两套不同的机制,那难道以前开发的视图就完全用不上了吗?
答案是可以的。
要在 SwiftUI 里使用 UIView 的子类,只需要把它用一个遵循 UIViewRepresentable
协议的 SwiftUI 视图包裹起来即可。
这里举的是 UIKit 的例子,但同样也适用于 AppKit 和 WatchKit。
我们再来创建一个新的 SwiftUI View 来做我们的地图界面,但这一次,我们要改一下内容视图的协议:
1
2
3
4
5
6
7
8
import SwiftUI
import MapKit // 引入 MapKit
struct MapView : UIViewRepresentable { // 把这里的协议改掉
var body: some View {
Text(“Hello World!”)
}
}
然后你会发现 Xcode 在 UIViewRepresentable
这里报错了,因为这个协议下有两个必须实现的方法:
makeUIView(context:)
用来创建我们的MKMapView
updateUIView(_:context:)
用来进行视图的配置,并响应后续可能的变化
那下面我们就来实现一下。再加新代码之前,可以把已经用不上的 body
部分先删掉了。
对于 makeUIView(context:)
,只需要声明它返回的是 MKMapView
然后直接通过构造方法返回一个空对象就可以了:
1
2
3
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
MKMapView(frame: .zero)
}
updateUIView(_:context:)
要做的事情就比较多了,我们一步步说:
1
2
3
4
5
6
7
8
9
func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<MapView>) {
// 1
let coordinate = CLLocationCoordinate2D(latitude: 23.112223, longitude: 113.331084)
// 2
let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
// 3
let region = MKCoordinateRegion(center: coordinate, span: span)
uiView.setRegion(region, animated: true)
}
- 先把表示广州塔坐标的对象给构造出来(这是我在网上查的,等会预览的时候看看准不准)
- 构造一个用来标识地图的缩放等级的对象,数值越小地图拉得越近
- 构造坐标区域,并把这个区域设置到我们的地图视图上
赶紧预览一下看看效果吧!你会发现画布上空白一片…
那是因为预览默认是静态模式的,它只能完整渲染 SwiftUI 的视图。因为我们现在用到了 UIView 的子类,所以要把预览切换到实时模式(右下角红框里的按钮):
emmmm…塔呢?这定位看起来也不是很准啊。
把一切都组装起来吧!
先看看预期实现的效果图:
然后花一点时间思考一下怎么弄,我们这两篇文章的知识是完全足够了的。
好啦!公布答案!
我们会在上一篇文章实现的 ContentView
里直接进行组装,下面来看看分解动作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct ContentView: View {
var body: some View {
VStack { // 1
MapView()
.edgesIgnoringSafeArea(.top) // 2.1
.frame(height: 300) // 2.2
CircleImage()
.offset(y: -100)
.padding(.bottom, -100) // 3
VStack(alignment: .leading) {
Text(“Hello SwiftUI”)
.font(.title)
HStack(alignment: .top) {
Text(“First Description”)
.font(.subheadline)
Spacer()
Text(“Second Description”)
.font(.subheadline)
}
}
.padding()
Spacer() // 4
}
}
}
- 先用一个
VStack
把所有内容包裹起来,默认情况下VStack
的内容布局是居中的,所以我们不需要做修改 - 针对
MapView
我们要做两个修改edgesIgnoringSafeArea
可以让我们的视图把系统预留给刘海和状态栏的区域也用掉,这样看起来会更自然一点- 对于只设置了
height
的视图,它的宽度会默认占满所有父视图里的可用空间
- 我们的图片视图本身已经实现了所有效果了,现在只需要调整一下位置即可。需要注意的是,因为这里是通过
offset
移动的,所以为了保持与底部文字的间距,特意加上了一个负的padding
来抵消掉位移导致的差距 - 做完前三步的操作后,这个
VStack
整体是在竖直方向上居中的,所以加上一个Spacer
把整体有内容的部分顶到最上面(其实也可以通过HStack
的alignment
来实现,不过那样代码就没有现在的简单优雅了)
最终成果:
如果你发现照着实现出来之后,中间圆形部分特别大的话,别担心,你是对的!
因为文章前面的CircleImage
确实是为了展示而特意设置得比较大的,所以调整一下里面的宽高即可。
到这里大家应该对 SwiftUI 的使用比较上手了,但目前为止涉及到的组件还比较少,SwiftUI 光是各种强大的组件就已经够玩很久了。不过我打算在第四篇文章里再集中讲各种有意思的组件使用方式,因为下一篇文章我们要先解决数据来源的问题。
既然我们的视图组件已经是通过声明式的写法来构建的了,那我们的数据是不是也该换一种方式绑定到视图上呢?在 JS 上我们可以用 react-redux 这样的数据绑定手段,那 SwiftUI 是不是该搭配 RxSwift 来使用了?
这些问题都将在下一篇文章里为大家解答!
SwiftUI | Creating and Combining Views
SwiftUI Essentials - WWDC 2019 - Videos - Apple Developer
Related Issues not found
Please contact @davidleee to initialize the comment
Recommend
-
44
At the current state of SwiftUI not all views are implemented yet. UIKit views can be added in the Swift hierarchy. In this tutorial a url will be displayed in a web view.SwiftUI requires Xcode 11 and MacOS Catalina, for...
-
28
A few weeks ago, we talked about building views like PagerView and BottomSheetView from scratch in SwiftUI . SwiftUI is pretty young and misses some components that we expect to hav...
-
20
咱们最有意思的第四篇 SwiftUI 教程来啦!为什么说是“最有意思”的呢?因为按照约定,在这篇文章里我们会一起来看看用 SwiftUI 开发界面的快捷便利体现在什么地方。相信这会让许多苹果开发者们耳目一新。 信了苹果教之后,每次有什么更新,...
-
9
UIKit or SwiftUI: what to use in production? 2020, Sep 29 Appl...
-
10
Integrating UIKit & SwiftUIGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupShapeGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGroupGrou...
-
16
Introspect for SwiftUI Introspect allows you to get the underlying UIKit or AppKit element of a SwiftUI view. For instance, with Introspect you can access UITableView to modify separator...
-
12
Replicate 12 UIKit's contentMode options in SwiftUI 12 Apr 2021 ⋅ 4 min read ⋅ SwiftUI
-
8
How to use SwiftUI in UIKit Using SwiftUI as UIView and UIViewController Table of ContentsIn the previous post (How to use UIKit in SwiftUI)...
-
3
访问 SwiftUI 内部的 UIKit 组件 Original...
-
7
在 SwiftUI 中使用 UIKit 视图已迈入第三个年头的 SwiftUI 相较诞生初始已经提供了更多的原生功能,但仍有大量的事情是无法直接通过原生 SwiftUI 代码来完成的。在相当长的时间中开发者仍需在 SwiftUI 中依赖 UIKit(AppKit)代码。好在,SwiftU...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK