MapApp 地图应用

1. 简述

  1.1 重点

    1)更好地理解 MVVM 架构

    2)更轻松地使用 SwiftUI 框架、对齐、动画和转换

  1.2 资源下载地址:

Swiftful-Thinking:icon-default.png?t=N7T8https://www.swiftful-thinking.com/downloads

  1.3 项目结构图:

  1.4 图片、颜色资源文件图:

  1.5 启动图片配置图:

2. Model 层

  2.1 创建模拟位置文件 Location.swift

import Foundation
import MapKitstruct Location: Identifiable, Equatable{let name: Stringlet cityName: Stringlet coordinates: CLLocationCoordinate2Dlet description: Stringlet imageNames: [String]let link: String// UUID().uuidString,生产的每个ID都不一样,为了保证有相同可识别的相同模型,使用名称加城市名称var id: String {name + cityName}// Equatable 判断 id 是否一样static func == (lhs: Location, rhs: Location) -> Bool {return lhs.id == rhs.id}
}

3. 数据服务层

  3.1 创建模拟位置数据信息服务 LocationsDataSerVice.swift

import Foundation
import MapKitclass LocationsDataService {static let locations: [Location] = [Location(name: "Colosseum",cityName: "Rome",coordinates: CLLocationCoordinate2D(latitude: 41.8902, longitude: 12.4922),description: "The Colosseum is an oval amphitheatre in the centre of the city of Rome, Italy, just east of the Roman Forum. It is the largest ancient amphitheatre ever built, and is still the largest standing amphitheatre in the world today, despite its age.",imageNames: ["rome-colosseum-1","rome-colosseum-2","rome-colosseum-3",],link: "https://en.wikipedia.org/wiki/Colosseum"),Location(name: "Pantheon",cityName: "Rome",coordinates: CLLocationCoordinate2D(latitude: 41.8986, longitude: 12.4769),description: "The Pantheon is a former Roman temple and since the year 609 a Catholic church, in Rome, Italy, on the site of an earlier temple commissioned by Marcus Agrippa during the reign of Augustus.",imageNames: ["rome-pantheon-1","rome-pantheon-2","rome-pantheon-3",],link: "https://en.wikipedia.org/wiki/Pantheon,_Rome"),Location(name: "Trevi Fountain",cityName: "Rome",coordinates: CLLocationCoordinate2D(latitude: 41.9009, longitude: 12.4833),description: "The Trevi Fountain is a fountain in the Trevi district in Rome, Italy, designed by Italian architect Nicola Salvi and completed by Giuseppe Pannini and several others. Standing 26.3 metres high and 49.15 metres wide, it is the largest Baroque fountain in the city and one of the most famous fountains in the world.",imageNames: ["rome-trevifountain-1","rome-trevifountain-2","rome-trevifountain-3",],link: "https://en.wikipedia.org/wiki/Trevi_Fountain"),Location(name: "Eiffel Tower",cityName: "Paris",coordinates: CLLocationCoordinate2D(latitude: 48.8584, longitude: 2.2945),description: "The Eiffel Tower is a wrought-iron lattice tower on the Champ de Mars in Paris, France. It is named after the engineer Gustave Eiffel, whose company designed and built the tower. Locally nicknamed 'La dame de fer', it was constructed from 1887 to 1889 as the centerpiece of the 1889 World's Fair and was initially criticized by some of France's leading artists and intellectuals for its design, but it has become a global cultural icon of France and one of the most recognizable structures in the world.",imageNames: ["paris-eiffeltower-1","paris-eiffeltower-2",],link: "https://en.wikipedia.org/wiki/Eiffel_Tower"),Location(name: "Louvre Museum",cityName: "Paris",coordinates: CLLocationCoordinate2D(latitude: 48.8606, longitude: 2.3376),description: "The Louvre, or the Louvre Museum, is the world's most-visited museum and a historic monument in Paris, France. It is the home of some of the best-known works of art, including the Mona Lisa and the Venus de Milo. A central landmark of the city, it is located on the Right Bank of the Seine in the city's 1st arrondissement.",imageNames: ["paris-louvre-1","paris-louvre-2","paris-louvre-3",],link: "https://en.wikipedia.org/wiki/Louvre"),]
}

4. ViewModel 层

  4.1 创建位置信息的 ViewModel LocationsViewModel.swift

import Foundation
import MapKit
import SwiftUIclass LocationsViewModel: ObservableObject{/// All loaded locations Published@Published var locationes: [Location] = []/// Current location on map@Published var mapLocation: Location {didSet {// 设置地图位置,然后更新地图区域updateMapRegion(location: mapLocation)}}/// Current region on map :  这是地图上的当前区域@Published var mapRegion: MKCoordinateRegion = MKCoordinateRegion()/// 坐标跨度let mapSpan = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)/// Show list of locations : 显示位置列表@Published var showLocationsList: Bool = false/// Show location detail via sheet : 显示位置详情信息页面@Published var sheetLocation: Location? = nilinit() {let locations = LocationsDataService.locationsself.locationes = locationsself.mapLocation = locations.first!self.updateMapRegion(location: locations.first!)}/// 更新地图区域private func updateMapRegion(location: Location){withAnimation(.easeInOut) {mapRegion = MKCoordinateRegion(// 中心点: 经纬度 latitude: 纬度  longitude: 经度center: location.coordinates,// 坐标跨度:span: mapSpan)}}/// 位置列表开关func toggleLocationsList(){withAnimation(.easeInOut) {// showLocationsList = !showLocationsListshowLocationsList.toggle()}}/// 显示下一个位置func showNextLocation(location: Location){withAnimation(.easeInOut) {mapLocation = locationshowLocationsList = false}}/// 下一个按钮处理事件func nextButtonPressed(){// Get the current indexguard let currentIndex = locationes.firstIndex(where: { $0 == mapLocation }) else {print("Could not find current index in locations array! Should naver happen.")return}// check if the nextIndex is valid: 检查下一个索引是否有校let nextIndex = currentIndex + 1guard locationes.indices.contains(nextIndex) else {// Next index is NOT avlid// Restart from 0guard let firstLocation = locationes.first else { return }showNextLocation(location: firstLocation)return}// Next index IS validlet nextLocation = locationes[nextIndex]showNextLocation(location: nextLocation)}
}

5. 创建 View 层

  5.1 位置列表 View

    1) 创建实现文件 LocationsListView.swift
import SwiftUI/// 位置列表
struct LocationsListView: View {/// 环境变量中的 ViewModel@EnvironmentObject private var viewMode: LocationsViewModelvar body: some View {List {ForEach(viewMode.locationes) { location inButton {viewMode.showNextLocation(location: location)} label: {listRowView(location: location)}.padding(.vertical, 4).listRowBackground(Color.clear)}}.listStyle(.plain)}
}extension LocationsListView {/// 列表行private func listRowView(location: Location) -> some View{HStack {if let imageName = location.imageNames.first {Image(imageName).resizable().scaledToFill().frame(width: 45, height: 45).cornerRadius(10)}VStack(alignment: .leading) {Text(location.name).font(.headline)Text(location.cityName).font(.headline)}.frame(maxWidth: .infinity, alignment: .leading)}}
}struct LocationsListView_Previews: PreviewProvider {static var previews: some View {LocationsListView().environmentObject(LocationsViewModel())}
}
    2) 效果图:

  5.2 位置预览 View

    1) 创建实现文件 LocationPreviewView.swift
import SwiftUI/// 位置预览视图
struct LocationPreviewView: View {/// 环境变量中配置的 viewModel@EnvironmentObject private var viewModel: LocationsViewModellet location: Locationvar body: some View {HStack(alignment:.bottom, spacing: 0) {VStack(alignment: .leading, spacing: 16) {imageSectiontitleSection}VStack(spacing: 8) {learnMoreButtonnextButton}}.padding(20).background(RoundedRectangle(cornerRadius: 10).fill(.ultraThinMaterial.opacity(0.7)).offset(y: 65)).cornerRadius(10)}
}extension LocationPreviewView {/// 图片部分private var imageSection: some View{ZStack {if let imageImage = location.imageNames.first{Image(imageImage).resizable().scaledToFill().frame(width: 100, height: 100).cornerRadius(10)}}.padding(6).background(Color.white).cornerRadius(10)}/// 标题部分private var titleSection: some View{VStack(alignment: .leading, spacing: 4) {Text(location.name).font(.title2).fontWeight(.bold)Text(location.cityName).font(.subheadline)}.frame(maxWidth: .infinity, alignment: .leading)}/// 了解更多按钮private var learnMoreButton: some View{Button {viewModel.sheetLocation = location} label: {Text("Learn more").font(.headline).frame(width: 125, height: 35)}.buttonStyle(.borderedProminent)}/// 下一个按钮private var nextButton: some View{Button {viewModel.nextButtonPressed()} label: {Text("Next").font(.headline).frame(width: 125, height: 35)}.buttonStyle(.bordered)}
}struct LocationPreviewView_Previews: PreviewProvider {static var previews: some View {ZStack {Color.black.ignoresSafeArea()LocationPreviewView(location: LocationsDataService.locations.first!).padding()}.environmentObject(LocationsViewModel())}
}
    2) 效果图:

  5.3 位置注释 View

    1) 创建实现文件 LocationMapAnnotationView.swift
import SwiftUI/// 位置注释视图
struct LocationMapAnnotationView: View {let accentColor = Color("AccentColor")var body: some View {VStack(spacing: 0) {Image(systemName: "map.circle.fill").resizable().scaledToFill().frame(width: 30, height: 30).font(.headline).foregroundColor(.white).padding(6).background(accentColor)//.cornerRadius(36).clipShape(Circle())Image(systemName: "triangle.fill").resizable().scaledToFill().foregroundColor(accentColor).frame(width: 10, height: 10).rotationEffect(Angle(degrees: 180)).offset(y: -3).padding(.bottom, 35)}}
}#Preview {ZStack{Color.black.ignoresSafeArea()LocationMapAnnotationView()}
}
    2) 效果图:

  5.4 主页 View

    1) 创建实现文件 LocationsView.swift
import SwiftUI
import MapKit/// 主页 View
struct LocationsView: View {@EnvironmentObject private var viewModel: LocationsViewModellet maxWidthForIpad: CGFloat = 700var body: some View {ZStack {mapLayer.ignoresSafeArea()VStack(spacing: 0) {header.padding().frame(maxWidth: maxWidthForIpad)Spacer()locationsPreviewStack}}// .fullScreenCover 全屏显示.sheet(item: $viewModel.sheetLocation) { location inLocationDetailView(location: location)}}
}extension LocationsView {/// 头Viewprivate var header: some View{VStack {Button {viewModel.toggleLocationsList()} label: {Text(viewModel.mapLocation.name + ", " + viewModel.mapLocation.cityName).font(.title2).fontWeight(.black).foregroundColor(.primary).frame(height: 55).frame(maxWidth: .infinity).animation(.none, value: viewModel.mapLocation).overlay(alignment: .leading) {Image(systemName: "arrow.down").font(.headline).foregroundColor(.primary).padding().rotationEffect(Angle(degrees: viewModel.showLocationsList ? 180 : 0))}}// 列表if viewModel.showLocationsList{LocationsListView()}}.background(.thickMaterial.opacity(0.7)).cornerRadius(10).shadow(color: Color.black.opacity(0.3), radius: 20, x: 0, y: 15)}/// 地图 Viewprivate var mapLayer: some View{Map(coordinateRegion: $viewModel.mapRegion,annotationItems: viewModel.locationes,annotationContent: { location in// 地图标识颜色// MapMarker(coordinate: location.coordinates, tint: .blue)// 自定义标识MapAnnotation(coordinate: location.coordinates) {LocationMapAnnotationView().scaleEffect(viewModel.mapLocation == location ? 1 : 0.7).shadow(radius: 10).onTapGesture {viewModel.showNextLocation(location: location)}}})}/// 地址预览堆栈private var locationsPreviewStack: some View{ZStack {ForEach(viewModel.locationes) { location in// 显示当前地址if viewModel.mapLocation == location {LocationPreviewView(location: location).shadow(color: Color.black.opacity(0.3), radius: 20).padding().frame(maxWidth: maxWidthForIpad).frame(maxWidth: .infinity)// .opacity// .transition(AnyTransition.scale.animation(.easeInOut))// 添加动画.transition(.asymmetric(insertion: .move(edge: .trailing),removal: .move(edge: .leading)))}}}}
}struct LocationsView_Previews: PreviewProvider {static var previews: some View {LocationsView().environmentObject(LocationsViewModel())}
}
    2) 效果图:

  5.5 位置详情页 View

    1) 创建实现文件 LocationDetailView.swift
import SwiftUI
import MapKit/// 位置详情页视图
struct LocationDetailView: View {// @Environment(\.presentationMode) var presentationMode// @Environment(\.dismiss) var dismiss@EnvironmentObject private var viewModel: LocationsViewModellet location: Locationvar body: some View {ScrollView {VStack {imageSectionVStack(alignment: .leading, spacing: 16){titleSectionDivider()descriptionSectionDivider()mapLayer}.frame(maxWidth: .infinity, alignment: .leading).padding()}}// 安全区.ignoresSafeArea()// 超薄材质,灰白色.background(.ultraThinMaterial)// 添加返回按钮.overlay(alignment: .topLeading) {backButton}}
}extension LocationDetailView{/// 滑动切换图private var imageSection: some View{TabView {ForEach(location.imageNames, id: \.self) {Image($0).resizable().scaledToFill().frame(width: UIDevice.current.userInterfaceIdiom == .pad  ? nil : UIScreen.main.bounds.width).clipped()}}.frame(height: 500).tabViewStyle(.page).shadow(color: .black.opacity(0.3), radius: 20, y: 10)}/// 标题视图private var titleSection: some View{VStack(alignment: .leading, spacing: 8){Text(location.name).font(.largeTitle).fontWeight(.semibold).foregroundStyle(.primary)Text(location.cityName).font(.title3).foregroundStyle(.secondary)}}/// 描述视图private var descriptionSection: some View{VStack(alignment: .leading, spacing: 16){Text(location.description).font(.subheadline).foregroundStyle(.secondary)if let url = URL(string: location.link) {Link("Read more on Wikipedia", destination: url).font(.headline).tint(.blue)}}}/// 地图 Viewprivate var mapLayer: some View{Map(coordinateRegion: .constant(MKCoordinateRegion(center: location.coordinates,span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))),annotationItems: [location]) { location inMapAnnotation(coordinate: location.coordinates){// 自定义标识LocationMapAnnotationView().shadow(radius: 10)}}.allowsHitTesting(false) // 禁止点击.aspectRatio(1, contentMode: .fit) // 纵横比.cornerRadius(30)}/// 返回按钮private var backButton: some View{Button{// dismiss.callAsFunction()viewModel.sheetLocation = nil} label: {Image(systemName: "xmark").font(.headline).padding(16).foregroundColor(.primary).background(.thickMaterial).cornerRadius(10).shadow(radius: 4).padding()}}
}#Preview {LocationDetailView(location: LocationsDataService.locations.first!).environmentObject(LocationsViewModel())
}
    2) 效果图:

  5.6 启动结构体文件 SwiftfulMapAppApp.swift

import SwiftUI@main
struct SwiftfulMapAppApp: App {@StateObject private var viewModel = LocationsViewModel()var body: some Scene {WindowGroup {LocationsView().environmentObject(viewModel)}}
}

6. 整体效果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/146742.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端JS 使用input完成文件上传操作,并对文件进行类型转换

使用input实现文件上传 // 定义一个用于文件上传的按钮<input type"file" name"upload1" />// accept属性用于定义允许上传的文件类型&#xff0c; onchange用于绑定文件上传之后的相应函数<input type"file" name"upload2"…

数据结构-链表的简单操作代码实现3-LinkedList【Java版】

写在前: 本篇博客主要介绍关于双向链表的一些简答操作实现&#xff0c;其中有有部分代码的实现和前两篇博客中的单向链表是相类似的。例如&#xff1a;查找链表中是否包含关键字key、求链表的长度等。 其余的涉及到prev指向的需要特别注意&#xff0c;区分和单向链表之间的差异…

uniapp heckbox-group实现多选

文章目录 html 代码JS 代码 混了业务逻辑&#xff0c;谨慎观看 html 代码 <view><!--可滚动视图区域。用于区域滚动 --><scroll-view :style"{ height: clientHeight px }" :scroll-top"scrollTop" scroll-y"true"scrolltouppe…

http的几种方法

http的几种方法在 rfc2616 中进行了定义&#xff1a; https://www.rfc-editor.org/rfc/rfc2616.html#page-51 HEAD方法&#xff1a;HEAD方法和GET方法相同&#xff0c;只不过服务端只返回头&#xff0c;不返回消息体。GET方法&#xff1a;用于获取资源POST方法&#xff1a;用于…

2013年11月10日 Go生态洞察:Go语言四周年回顾

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

招聘小程序源码 人才招聘网源码

招聘小程序源码 人才招聘网源码 求职招聘小程序源码系统是一种基于微信小程序的招聘平台&#xff0c;它可以帮助企业和求职者快速、方便地进行招聘和求职操作。 该系统通常包括以下功能模块&#xff1a; 用户注册和登录&#xff1a;用户可以通过微信小程序注册和登录&#…

javascript选择器的封装,只需要写元素名或css类及id都可以选择到元素

//模仿jquery选择器样式&#xff0c;只需要写元素名或css类及id都可以选择到元素 <html><head><meta http-equiv"Content-Type:text/html;charsetutf8"/><link rel"shortcut icon" href"#"/><title>封装选择器&l…

MySQL中json类型,你使用过吗

在最近的项目开发过程中&#xff0c;遇到了消息发送内容以Map形式存储的情况。最初的解决方案是将对象转换为字符串&#xff0c;并存储在MySQL的varchar(3000)字段中。然而&#xff0c;由于对存储空间的限制&#xff0c;不得不寻找其他解决方案。在调研中发现&#xff0c;从MyS…

如何利用ChatGPT撰写学术论文?

在阅读全文前请注意&#xff0c;本文是利用ChatGPT“辅助完成”而不是“帮写”学术论文&#xff0c;请一定要注意学术规范&#xff01; 本文我将介绍如何使用清晰准确的“指令”让ChatGPT帮助我们在论文写作上提高效率&#xff0c;希望通过本文的指导&#xff0c;读者能够充分…

uniapp app tabbar 页面默认隐藏

1.在page.json 中找到tabbar visible 默认为true,设为false则是不显示 uni.setTabBarItem({ index: 1, //列表索引 visible:true //显示或隐藏 })

Linux环境的Windows子系统

Linux环境的Windows 子系统 (WSL) 可让开发人员直接在 Windows 上按原样运行 GNU/Linux 环境&#xff08;包括大多数命令行工具、实用工具和应用程序&#xff09;&#xff0c;且不会产生传统虚拟机或双启动设置开销。 1、启用适用于 Linux 的 Windows 子系统 dism.exe /online …

PyTorch

正常界面 创建环境 conda create -n env_test python3.6进入环境 conda activate env_testpycharm中&#xff0c;创建项目&#xff0c;选择环境

Ubuntu18.04安装IgH主站

EtherCAT主站是EtherCAT网络中的中央控制单元,负责协调和管理连接到网络的所有从站设备。EtherCAT(Ethernet for Control Automation Technology)是一种高性能、实时的工业以太网通信协议,广泛应用于自动化和控制领域。 一、安装依赖包 sudo apt install autoconf automa…

LeetCode(26)判断子序列【双指针】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 判断子序列 1.题目 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;…

【Java】恺撒密码,stream流,方法引用

文章目录 一、题目二、题解2.1、写法12.2、写法2&#xff0c;stream流2.3、写法3&#xff0c;方法引用 一、题目 二、题解 2.1、写法1 普通写法, 遍历每个字符进行加密 public static void main1 (String[] args) {Scanner sc new Scanner(System.in);String strs sc.nextL…

Linux 进程管理 实时调度类及SMP和NUMA

文章目录 一、 实时调度类分析1.1 实时调度实体sched_rt_entity数据结构1.2 实时调度类rt_sched_class数据结构1.3 实时调度类功能函数 二、SMP和NUMA2.1 SMP&#xff08;多对称处理器结构&#xff0c;UMA&#xff09;2.2 NUMA&#xff08;非一致内存访问结构&#xff09;2.3 C…

JS 防抖封装方法

防抖封装方法&#xff1a; /*** param {Function} func* param {number} wait* param {boolean} immediate* return {*}*/ export function debounce(func, wait, immediate) {let timeout, args, context, timestamp, resultconst later function () {// 据上一次触发时间间隔…

【Linux系统化学习】进程的父子关系 | fork 进程

个人主页点击直达&#xff1a;小白不是程序媛 Linux专栏&#xff1a;Linux系统化学习 目录 前言&#xff1a; 父子进程 父子进程的引入 查看父子进程 查询进程的动态目录 更改进程的工作目录 fork创建进程 fork的引入 fork的使用 fork的原理 fork如何实现的&#…

PS学习笔记——图层

文章目录 图层面板图层类型新建图层新建方式图层颜色 操作图层修改图层名称选中图层隐藏图层调整图层顺序复制图层 图层面板 按F7可打开/关闭图层面板 该面板就是图层面板了 对所有图层进行筛选的按钮&#xff0c;第一个搜索框可以选择按什么方式进行筛选&#xff0c;支持&am…

html-网站菜单-点击显示导航栏

一、效果图 1.点击显示菜单栏&#xff0c;点击x号关闭&#xff1b; 2.点击一级菜单&#xff0c;展开显示二级&#xff0c;并且加号变为减号&#xff1b; 3.点击其他一级导航&#xff0c;自动收起展开的导航。 二、代码实现 <!DOCTYPE html> <html><head>&…