阅前引导
通过阅读本文章,可以了解一下内容:
- 实现简单的引导页面。
- 简单的动画效果,缩放,绘画图形。
- 在地图上标记点
功能简述
- 功能包含引导启动页面
- 招采、项目列表和详情
- 地图和项目标记
- 我的页面
主要功能功能实现
引导页面
@AppStorage是SwiftUI框架提供的一个属性包装器,设计初衷是创建一种在视图中保存和读取UserDefaults变量的快捷方法。@AppStorage在视图中的行为同@State很类似,其值变化时将导致与其依赖的视图无效并进行重新绘制。
这里我们使用@AppStorage来标记是否展示引导页面,另外我们给引导页面的图片增加一个动画效果, 当图片首次展现时,对图片进行缩放scaleEffect。右滑切换引导页面,点击体验按钮进入首页。
@State private var isAnimating: Bool = false
var openImageName: String
var openImageTitle: String
@AppStorage("isOnboarding") varisOnboarding: Bool?
VStack(spacing: 20) {
Image(openImageName)
.resizable()
.scaledToFit()
.scaleEffect(isAnimating ? 1.0 : 0.6)
.cornerRadius(20)
Button {
isOnboarding = **false**
} label: {
HStack(spacing: 8) {
Text("立即体验")
Image(systemName: "arrow.right.circle")
.font(.largeTitle)
}
.padding(.horizontal, 16)
.padding(.vertical, 10)
}
.accentColor(.black)
Text("RCC@\(openImageTitle)")
.font(.title)
.fontWeight(.semibold)
} //: ZStack
.onAppear{
withAnimation(.easeOut(duration: 0.5)) {
isAnimating = true
}
}
.padding(.horizontal, 20)
列表和详情页面
该部分有三个view页面组成,item单条记录view,列表view,详情页view, 列表使用List组件和NavigationView组件,使用NavigationLink进行页面跳转
单条记录View
struct ProjectItemView: View {
let project: Project
var body: some View {
VStack(alignment: .leading, spacing: 15) {
Text(project.project_name)
.font(.title3)
.fontWeight(.heavy)
.lineLimit(1)
HStack (alignment: .center, spacing: 10) {
Text(project.category)
.font(.headline)
.foregroundColor(.blue)
Text(project.city)
.font(.headline)
.foregroundColor(.blue)
Spacer()
}
Text("公布时间:\(project.new_info_date)")
.font(.headline)
.foregroundColor(.blue)
}
}
}
list列表view
struct ProjectItemListView: View {
@State var projects:[Project] = Bundle.main.decode("projects.json")
var body: some View {
NavigationView {
List {
ForEach(projects) {project **in**
NavigationLink(destination: ProjectDetailView(project: project)) {
ProjectItemView(project: project)
.padding(.vertical, 8)
}
}
.listStyle(InsetListStyle())
.navigationTitle("工程信息")
.navigationBarTitleDisplayMode(.inline)
}
}
}
}
地图标记页面
地图页面部分使用MapKit框架,这样才能使用苹果系统提供的地图相关功能,添加一个region变量,类型是MKCoordinateRegion,必须提供两个重要的信息才能保证地图的正常运行,一是地图中心坐标,经度和维度,使用CLLocationCoordinate2D,二是地图的坐标区域,使用MKCoordinateSpan。
struct MapView: View {
@State private var region: MKCoordinateRegion = {
var mapCoordinates = CLLocationCoordinate2D(latitude: 39.8997255, longitude: 116.3743353)
var mapZoomLevel = MKCoordinateSpan(latitudeDelta: 0.045, longitudeDelta: 0.045)
var mapRegin = MKCoordinateRegion(center: mapCoordinates, span: mapZoomLevel)
return mapRegin
}()
let locations: [Place] = Bundle.main.decode("places.json")
var body: some View {
Map(coordinateRegion: $region,annotationItems: locations) { item in
// MapPin(coordinate: item.location, tint: .accentColor)
// MapMarker(coordinate: item.location, tint: .accentColor)
MapAnnotation(coordinate: item.location) {
MapAnnotationView(loaction: item)
}
}
}
}
在地图上遍历绘制元素,MapPin、MapMarker是使用系统默认的图标,MapAnnotation 是自定义展示标注。这里我们给标注增了个同心圆的放大消失效果。在ZStack中上层定义一个圆,下层再定义一个小一点的圆,让这个圆的大小随时间变大到和上层圆一样,但是透明度从1变到0,让其一直重复。
var loaction: Place
@State private var animation: Double = 0.0
var body: some View {
ZStack {
Circle()
.frame(width: 34, height: 34, alignment: .center)
Circle()
.stroke(Color.accentColor,lineWidth: 2)
.frame(width: 32, height: 32, alignment: .center)
.scaleEffect(1 + CGFloat(animation))
.opacity(1 - animation)
Image("firm-head")
.resizable()
.scaledToFit()
.frame(width: 32, height: 32, alignment: .center)
.clipShape(Circle())
} //: ZStack
.onAppear{
withAnimation (
Animation.easeOut(duration: 2).repeatForever(autoreverses:false)
) {
animation = 1
}
}
}
我的页面
这个部分基础信息部分使用Form和Section结合,跳转和上面的列表页面一样。头像部分clipShape裁剪个圆形,并在上面覆盖一个宽带1的圆环。
Image("skye")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100)
.clipShape(Circle())
.overlay(
Circle().stroke(Color(.systemGray5), lineWidth: 1)
)
背景动画部分,GeometryReader是一个容器视图,可以根据其的自身大小和坐标空间定义其内容。图形绘制:圆随机个数,随机速度,随机缩放,随机尺寸,随机坐标,随机掩饰,stiffness 、damping设置阻尼效果,使用drawingGroup修饰器,属于Metal苹果系统高级框架,可以直接工作再GPU上,图形复杂不再影响性能问题。
@State private var randomCircle = Int.random(in: 12...16)
@State private var isAnimation = false
// 1. 随机坐标
func randomCoordinate(max: CGFloat) -> CGFloat {
return CGFloat.random(in: 0...max)
}
// 2. 随机尺寸
func randomSize() -> CGFloat {
return CGFloat.random(in: 10...300)
}
// 3.随机缩放比
func randomScale() -> CGFloat {
return CGFloat.random(in: 0.1...2.0)
}
// 4.随机速度
func randomSpeed() -> Double {
Double.random(in: 0.025...1.0)
}
// 5.随机掩饰
func randomDelay() -> Double {
Double.random(in: 0...2)
}
var body: some View {
GeometryReader { geometry in
ZStack {
ForEach( 0 ..< randomCircle, id: \.**self**) { item in
Circle()
.foregroundColor(.blue)
.opacity(0.15)
.frame(width: randomSize(), height: randomSize(), alignment: .center)
.scaleEffect(isAnimation ? randomScale() : 1)
.position(x: randomCoordinate(max: geometry.size.width), y: randomCoordinate(max: geometry.size.height))
.animation(
Animation.interpolatingSpring(stiffness: 0.5, damping: 0.5)
.repeatForever()
.speed(randomSpeed())
.delay(randomDelay())
)
.onAppear{
isAnimation = true
}
}
}
.drawingGroup()
}
}
总结
- 学习到了如何在地图上添加标注的三种方式
- 学习了如何简单添加缩放的动画效果
- 学习了如何自定义绘制动画图形