版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.06.19 星期五 |
前言
MapKit框架直接从您的应用界面显示地图或卫星图像,调出兴趣点,并确定地图坐标的地标信息。接下来几篇我们就一起看一下这个框架。感兴趣的看下面几篇文章。
1. MapKit框架详细解析(一) —— 基本概览(一)
2. MapKit框架详细解析(二) —— 基本使用简单示例(一)
3. MapKit框架详细解析(三) —— 基本使用简单示例(二)
4. MapKit框架详细解析(四) —— 一个叠加视图相关的简单示例(一)
5. MapKit框架详细解析(五) —— 一个叠加视图相关的简单示例(二)
6. MapKit框架详细解析(六) —— 添加自定义图块(一)
7. MapKit框架详细解析(七) —— 添加自定义图块(二)
8. MapKit框架详细解析(八) —— 添加自定义图块(三)
9. MapKit框架详细解析(九) —— 地图特定区域放大和创建自定义地图annotations(一)
10. MapKit框架详细解析(十) —— 地图特定区域放大和创建自定义地图annotations(二)
11. MapKit框架详细解析(十一) —— 自定义MapKit Tiles(一)
源码
1. Swift
首先看下工程组织结构
下面就是sb中的内容
接着就是源码了
1. UIAlertController+Extension.swift
import UIKit
extension UIAlertController {
func addActions(actions: [UIAlertAction]) {
actions.forEach { action in
self.addAction(action)
}
}
}
2. PointOfInterest+MapKit.swift
import Foundation
import MapKit
extension PointOfInterest: MKAnnotation {
var coordinate: CLLocationCoordinate2D { return location.coordinate }
var title: String? { return name }
}
3. Game.swift
import UIKit
import CoreLocation
let encounterRadius: CLLocationDistance = 10 //meters
enum FightResult {
case heroWon, heroLost, tie
}
enum ItemResult {
case purchased, notEnoughMoney
}
let gameStateNotification = Notification.Name("GameUpdated")
protocol GameDelegate: class {
func encounteredMonster(monster: Monster)
func encounteredNPC(npc: NPC)
func enteredStore(store: Store)
}
class Game {
static let shared = Game()
var adventurer: Adventurer?
var pointsOfInterest: [PointOfInterest] = []
var lastPOI: PointOfInterest?
var warps: [WarpZone] = []
var reservoir: [CLLocationCoordinate2D] = []
weak var delegate: GameDelegate?
init() {
adventurer = Adventurer(name: "Hero", hitPoints: 10, strength: 10)
setupPOIs()
setupWarps()
setupResevoir()
}
private func setupPOIs() {
pointsOfInterest = [
.appleStore,
.balto,
.boatHouse,
.castle,
.cloisters,
.hamilton,
.obelisk,
.met,
.strawberryFields,
.statueOfLiberty,
.tavernOnGreen,
.timesSquare,
.zoo
]
}
// swiftlint:disable discouraged_object_literal
private func setupWarps() {
warps = [
WarpZone(latitude: 40.765158, longitude: -73.974774, color: #colorLiteral(red: 0.9882352941, green: 0.8, blue: 0.03921568627, alpha: 1)),
WarpZone(latitude: 40.768712, longitude: -73.981590, color: #colorLiteral(red: 1, green: 0.3882352941, blue: 0.09803921569, alpha: 1)),
WarpZone(latitude: 40.768712, longitude: -73.981590, color: #colorLiteral(red: 0, green: 0.2235294118, blue: 0.6509803922, alpha: 1)),
WarpZone(latitude: 40.776219, longitude: -73.976247, color: #colorLiteral(red: 1, green: 0.3882352941, blue: 0.09803921569, alpha: 1)),
WarpZone(latitude: 40.776219, longitude: -73.976247, color: #colorLiteral(red: 0, green: 0.2235294118, blue: 0.6509803922, alpha: 1)),
WarpZone(latitude: 40.781987, longitude: -73.972020, color: #colorLiteral(red: 1, green: 0.3882352941, blue: 0.09803921569, alpha: 1)),
WarpZone(latitude: 40.781987, longitude: -73.972020, color: #colorLiteral(red: 0, green: 0.2235294118, blue: 0.6509803922, alpha: 1)),
WarpZone(latitude: 40.785253, longitude: -73.969638, color: #colorLiteral(red: 1, green: 0.3882352941, blue: 0.09803921569, alpha: 1)),
WarpZone(latitude: 40.785253, longitude: -73.969638, color: #colorLiteral(red: 0, green: 0.2235294118, blue: 0.6509803922, alpha: 1)),
WarpZone(latitude: 40.791605, longitude: -73.964853, color: #colorLiteral(red: 1, green: 0.3882352941, blue: 0.09803921569, alpha: 1)),
WarpZone(latitude: 40.791605, longitude: -73.964853, color: #colorLiteral(red: 0, green: 0.2235294118, blue: 0.6509803922, alpha: 1)),
WarpZone(latitude: 40.796089, longitude: -73.961463, color: #colorLiteral(red: 1, green: 0.3882352941, blue: 0.09803921569, alpha: 1)),
WarpZone(latitude: 40.796089, longitude: -73.961463, color: #colorLiteral(red: 0, green: 0.2235294118, blue: 0.6509803922, alpha: 1)),
WarpZone(latitude: 40.799988, longitude: -73.958480, color: #colorLiteral(red: 1, green: 0.3882352941, blue: 0.09803921569, alpha: 1)),
WarpZone(latitude: 40.799988, longitude: -73.958480, color: #colorLiteral(red: 0, green: 0.2235294118, blue: 0.6509803922, alpha: 1)),
WarpZone(latitude: 40.798493, longitude: -73.952622, color: #colorLiteral(red: 0.9333333333, green: 0.2078431373, blue: 0.1803921569, alpha: 1)),
WarpZone(latitude: 40.755238, longitude: -73.987405, color: #colorLiteral(red: 0.7254901961, green: 0.2, blue: 0.6784313725, alpha: 1)),
WarpZone(latitude: 40.754344, longitude: -73.987105, color: #colorLiteral(red: 0.9882352941, green: 0.8, blue: 0.03921568627, alpha: 1)),
WarpZone(latitude: 40.865757, longitude: -73.927088, color: #colorLiteral(red: 0, green: 0.2235294118, blue: 0.6509803922, alpha: 1)),
WarpZone(latitude: 40.701789, longitude: -74.013004, color: #colorLiteral(red: 0.9333333333, green: 0.2078431373, blue: 0.1803921569, alpha: 1))
]
}
// swiftlint:enable discouraged_object_literal
// swiftlint:disable:next function_body_length
private func setupResevoir() {
reservoir = [
CLLocationCoordinate2D(latitude: 40.78884, longitude: -73.95857),
CLLocationCoordinate2D(latitude: 40.78889, longitude: -73.95824),
CLLocationCoordinate2D(latitude: 40.78882, longitude: -73.95786),
CLLocationCoordinate2D(latitude: 40.78867, longitude: -73.95758),
CLLocationCoordinate2D(latitude: 40.78838, longitude: -73.95749),
CLLocationCoordinate2D(latitude: 40.78793, longitude: -73.95764),
CLLocationCoordinate2D(latitude: 40.78744, longitude: -73.95777),
CLLocationCoordinate2D(latitude: 40.78699, longitude: -73.95777),
CLLocationCoordinate2D(latitude: 40.78655, longitude: -73.95779),
CLLocationCoordinate2D(latitude: 40.78609, longitude: -73.95818),
CLLocationCoordinate2D(latitude: 40.78543, longitude: -73.95867),
CLLocationCoordinate2D(latitude: 40.78469, longitude: -73.95919),
CLLocationCoordinate2D(latitude: 40.78388, longitude: -73.95975),
CLLocationCoordinate2D(latitude: 40.78325, longitude: -73.96022),
CLLocationCoordinate2D(latitude: 40.78258, longitude: -73.96067),
CLLocationCoordinate2D(latitude: 40.78227, longitude: -73.96101),
CLLocationCoordinate2D(latitude: 40.78208, longitude: -73.96136),
CLLocationCoordinate2D(latitude: 40.782, longitude: -73.96172),
CLLocationCoordinate2D(latitude: 40.78201, longitude: -73.96202),
CLLocationCoordinate2D(latitude: 40.78214, longitude: -73.96247),
CLLocationCoordinate2D(latitude: 40.78237, longitude: -73.96279),
CLLocationCoordinate2D(latitude: 40.78266, longitude: -73.96309),
CLLocationCoordinate2D(latitude: 40.7832, longitude: -73.96331),
CLLocationCoordinate2D(latitude: 40.78361, longitude: -73.96363),
CLLocationCoordinate2D(latitude: 40.78382, longitude: -73.96395),
CLLocationCoordinate2D(latitude: 40.78401, longitude: -73.96453),
CLLocationCoordinate2D(latitude: 40.78416, longitude: -73.96498),
CLLocationCoordinate2D(latitude: 40.78437, longitude: -73.9656),
CLLocationCoordinate2D(latitude: 40.78456, longitude: -73.96601),
CLLocationCoordinate2D(latitude: 40.78479, longitude: -73.96636),
CLLocationCoordinate2D(latitude: 40.78502, longitude: -73.96661),
CLLocationCoordinate2D(latitude: 40.78569, longitude: -73.96659),
CLLocationCoordinate2D(latitude: 40.78634, longitude: -73.9664),
CLLocationCoordinate2D(latitude: 40.78705, longitude: -73.96623),
CLLocationCoordinate2D(latitude: 40.78762, longitude: -73.96603),
CLLocationCoordinate2D(latitude: 40.78791, longitude: -73.96571),
CLLocationCoordinate2D(latitude: 40.78816, longitude: -73.96533),
CLLocationCoordinate2D(latitude: 40.78822, longitude: -73.9649),
CLLocationCoordinate2D(latitude: 40.7882, longitude: -73.96445),
CLLocationCoordinate2D(latitude: 40.78819, longitude: -73.96404),
CLLocationCoordinate2D(latitude: 40.78814, longitude: -73.96378),
CLLocationCoordinate2D(latitude: 40.7882, longitude: -73.96354),
CLLocationCoordinate2D(latitude: 40.78819, longitude: -73.96327),
CLLocationCoordinate2D(latitude: 40.78817, longitude: -73.96301),
CLLocationCoordinate2D(latitude: 40.7882, longitude: -73.96269),
CLLocationCoordinate2D(latitude: 40.7882, longitude: -73.96245),
CLLocationCoordinate2D(latitude: 40.7883, longitude: -73.96217),
CLLocationCoordinate2D(latitude: 40.7885, longitude: -73.96189),
CLLocationCoordinate2D(latitude: 40.78874, longitude: -73.96161),
CLLocationCoordinate2D(latitude: 40.78884, longitude: -73.96127),
CLLocationCoordinate2D(latitude: 40.78885, longitude: -73.96093),
CLLocationCoordinate2D(latitude: 40.78879, longitude: -73.9606),
CLLocationCoordinate2D(latitude: 40.78869, longitude: -73.96037),
CLLocationCoordinate2D(latitude: 40.78864, longitude: -73.96009),
CLLocationCoordinate2D(latitude: 40.78863, longitude: -73.95972),
CLLocationCoordinate2D(latitude: 40.78863, longitude: -73.95936),
CLLocationCoordinate2D(latitude: 40.78867, longitude: -73.95895)
]
}
func visitedLocation(location: CLLocation) {
guard let currentPOI = poiAtLocation(location: location) else { return }
if currentPOI.isRegenPoint {
regenAdventurer()
}
switch currentPOI.encounter {
case let npc as NPC:
delegate?.encounteredNPC(npc: npc)
case let monster as Monster:
delegate?.encounteredMonster(monster: monster)
case let store as Store:
delegate?.enteredStore(store: store)
default:
break
}
}
func poiAtLocation(location: CLLocation) -> PointOfInterest? {
for point in pointsOfInterest {
let center = point.location
let distance = abs(location.distance(from: center))
if distance < encounterRadius {
//debounce staying in the same spot for awhile
if point != lastPOI {
lastPOI = point
return point
} else {
return nil
}
}
}
lastPOI = nil
return nil
}
func regenAdventurer() {
guard let adventurer = adventurer else { return }
adventurer.hitPoints = adventurer.maxHitPoints
adventurer.isDefeated = false
}
func fight(monster: Monster) -> FightResult? {
guard let adventurer = adventurer else { return nil }
defer { NotificationCenter.default.post(name: gameStateNotification, object: self) }
//give the hero a fighting chance
monster.hitPoints -= adventurer.strength
if monster.hitPoints <= 0 {
adventurer.gold += monster.gold
return .heroWon
}
adventurer.hitPoints -= monster.strength
if adventurer.hitPoints <= 0 {
adventurer.isDefeated = true
return .heroLost
}
return .tie
}
func purchaseItem(item: Item) -> ItemResult? {
guard let adventurer = adventurer else { return nil }
defer { NotificationCenter.default.post(name: gameStateNotification, object: self) }
if adventurer.gold >= item.cost {
adventurer.gold -= item.cost
adventurer.inventory.append(item)
return .purchased
} else {
return .notEnoughMoney
}
}
}
extension Game {
func image(for monster: Monster) -> UIImage? {
switch monster.name {
case Monster.goblin.name:
return UIImage(named: "goblin")
case NPC.king.name:
return UIImage(named: "king")
default:
return nil
}
}
func image(for store: Store) -> UIImage? {
return UIImage(named: "store")
}
func image(for item: Item) -> UIImage? {
switch item.name {
case Weapon.sword6Plus.name:
return UIImage(named: "sword")
default:
return nil
}
}
}
4. Monster.swift
import Foundation
class Monster {
// MARK: - Properties
let name: String
var hitPoints: Int
var baseStrength: Int
var gold: Int
var strength: Int { return baseStrength }
// MARK: - Initializers
init(name: String, hitPoints: Int, strength: Int, gold: Int = 0) {
self.name = name
self.hitPoints = hitPoints
self.baseStrength = strength
self.gold = gold
}
}
extension Monster {
static let goblin = Monster(name: "Goblin", hitPoints: 1, strength: 1, gold: 10)
}
5. NPC.swift
import Foundation
class NPC: Monster {
// MARK: - Properties
let quest: String
// MARK: - Initializers
init(quest: String, name: String) {
self.quest = quest
super.init(name: name, hitPoints: 0, strength: 0)
}
}
extension NPC {
static let king = NPC(quest: "Bring me the ears of ten goblins, and you'll get a great reward", name: "King")
}
6. Adventurer.swift
import Foundation
class Adventurer: Monster {
// MARK: - Properties
var isDefeated = false
var maxHitPoints: Int = 0
var inventory: [Item] = []
override var strength: Int {
// swiftlint:disable:next force_cast
return baseStrength + inventory.filter { $0 is Weapon }.reduce(0) { max($0, ($1 as! Weapon).strength) }
}
// MARK: - Initializers
override init(name: String, hitPoints: Int, strength: Int, gold: Int = 100) {
super.init(name: name, hitPoints: hitPoints, strength: strength, gold: gold)
maxHitPoints = hitPoints
}
}
7. PointOfInterest.swift
import Foundation
import CoreLocation
class PointOfInterest: NSObject { //has to be NSObject to use with MKAnnotation ... boo :(
// MARK: - Properties
let location: CLLocation
let name: String
let isRegenPoint: Bool
let encounter: Encounter?
// MARK: - Initializers
init(name: String, location: CLLocation, isRegenPoint: Bool, encounter: Encounter? = nil) {
self.name = name
self.location = location
self.isRegenPoint = isRegenPoint
self.encounter = encounter
}
}
// swiftlint:disable line_length
extension PointOfInterest {
static let appleStore = PointOfInterest(name: "\"Fruit\" Store", location: CLLocation(latitude: 40.763560, longitude: -73.972321), isRegenPoint: true, encounter: Store.appleStore)
static let balto = PointOfInterest(name: "Balto Statue", location: CLLocation(latitude: 40.7699631, longitude: -73.9732103), isRegenPoint: true)
static let boatHouse = PointOfInterest(name: "Entrance to Water Level", location: CLLocation(latitude: 40.7772265, longitude: -73.972275), isRegenPoint: true)
static let castle = PointOfInterest(name: "Castle", location: CLLocation(latitude: 40.7794379, longitude: -73.9712102), isRegenPoint: false, encounter: NPC.king)
static let cloisters = PointOfInterest(name: "Monastery", location: CLLocation(latitude: 40.8648668, longitude: -73.9339161), isRegenPoint: false)
static let hamilton = PointOfInterest(name: "Warrior's Memorial", location: CLLocation(latitude: 40.7796328, longitude: -73.9676018), isRegenPoint: false)
static let met = PointOfInterest(name: "Art Palace", location: CLLocation(latitude: 40.7790478, longitude: -73.96627832), isRegenPoint: false)
static let obelisk = PointOfInterest(name: "Obelisk", location: CLLocation(latitude: 40.7796328, longitude: -73.9676018), isRegenPoint: false)
static let statueOfLiberty = PointOfInterest(name: "Colossus", location: CLLocation(latitude: 40.6892534, longitude: -74.0466891), isRegenPoint: false)
static let strawberryFields = PointOfInterest(name: "Imagine Fields", location: CLLocation(latitude: 40.775556, longitude: -73.975), isRegenPoint: true)
static let tavernOnGreen = PointOfInterest(name: "Tavern", location: CLLocation(latitude: 40.7721909, longitude: -73.9799102), isRegenPoint: true)
static let timesSquare = PointOfInterest(name: "Town", location: CLLocation(latitude: 40.758899, longitude: -73.9873197), isRegenPoint: false)
static let zoo = PointOfInterest(name: "Monster Menagerie", location: CLLocation(latitude: 40.767769, longitude: -73.971870), isRegenPoint: false, encounter: Monster.goblin)
}
8. Encounter.swift
import Foundation
protocol Encounter {
}
extension Monster: Encounter {
}
extension Store: Encounter {
}
9. Store.swift
import Foundation
class Store {
// MARK: - Properties
let name: String
var inventory: [Item]
// MARK: - Initializers
init(name: String, items: [Item]) {
self.name = name
self.inventory = items
}
}
extension Store {
static let appleStore = Store(name: "The \"Fruit\" Store", items: [Weapon.sword6Plus])
}
10. Item.swift
import Foundation
class Item {
// MARK: - Properties
let name: String
let cost: Int
// MARK: - Initializers
init(name: String, cost: Int) {
self.cost = cost
self.name = name
}
}
11. Weapon.swift
import Foundation
class Weapon: Item {
// MARK: - Properties
let strength: Int
// MARK: - Initializers
init(name: String, cost: Int, strength: Int) {
self.strength = strength
super.init(name: name, cost: cost)
}
}
extension Weapon {
static let sword6Plus = Weapon(name: "Sword 6+", cost: 50, strength: 6)
}
12. AppDelegate.swift
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Game.shared.adventurer = Adventurer(name: "Hero", hitPoints: 10, strength: 10, gold: 40)
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
}
13. SceneDelegate.swift
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
let locationListener = LocationListener()
}
14. LocationListener.swift
import Foundation
import CoreLocation
class LocationListener: NSObject {
// MARK: - Properties
let manager = CLLocationManager()
// MARK: - Initializers
override init() {
super.init()
manager.delegate = self
manager.activityType = .other
manager.requestWhenInUseAuthorization()
}
}
// MARK: - CLLocationManagerDelegate
extension LocationListener: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
manager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let lastLocation = locations.last else { return }
Game.shared.visitedLocation(location: lastLocation)
}
}
15. MapViewController.swift
import UIKit
import MapKit
class MapViewController: UIViewController {
// MARK: - IBOutlets
@IBOutlet weak var mapView: MKMapView!
@IBOutlet weak var heartsLabel: UILabel!
// MARK: - Properties
// swiftlint:disable implicitly_unwrapped_optional
var tileRenderer: MKTileOverlayRenderer!
var shimmerRenderer: ShimmerRenderer!
// swiftlint:enable implicitly_unwrapped_optional
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
setupTileRenderer()
setupLakeOverlay()
let initialRegion = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 40.774669555422349, longitude: -73.964170794293238),
span: MKCoordinateSpan(latitudeDelta: 0.16405544070813249, longitudeDelta: 0.1232528799585566))
mapView.cameraZoomRange = MKMapView.CameraZoomRange(
minCenterCoordinateDistance: 7000,
maxCenterCoordinateDistance: 60000)
mapView.cameraBoundary = MKMapView.CameraBoundary(coordinateRegion: initialRegion)
mapView.region = initialRegion
mapView.showsUserLocation = true
mapView.showsCompass = true
mapView.setUserTrackingMode(.followWithHeading, animated: true)
Game.shared.delegate = self
NotificationCenter.default
.addObserver(self, selector: #selector(gameUpdated(notification:)), name: gameStateNotification, object: nil)
mapView.delegate = self
mapView.addAnnotations(Game.shared.warps)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
renderGame()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "shop",
let shopController = segue.destination as? ShopViewController,
let store = sender as? Store {
shopController.shop = store
}
}
private func setupTileRenderer() {
let overlay = AdventureMapOverlay()
overlay.canReplaceMapContent = true
mapView.addOverlay(overlay, level: .aboveLabels)
tileRenderer = MKTileOverlayRenderer(tileOverlay: overlay)
overlay.minimumZ = 13
overlay.maximumZ = 16
}
private func setupLakeOverlay() {
let lake = MKPolygon(coordinates: &Game.shared.reservoir, count: Game.shared.reservoir.count)
mapView.addOverlay(lake)
shimmerRenderer = ShimmerRenderer(overlay: lake)
shimmerRenderer.fillColor = #colorLiteral(red: 0.2431372549, green: 0.5803921569, blue: 0.9764705882, alpha: 1)
// swiftlint:disable:previous discouraged_object_literal
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
self?.shimmerRenderer.updateLocations()
self?.shimmerRenderer.setNeedsDisplay()
}
}
@objc func gameUpdated(notification: Notification) {
renderGame()
}
}
// MARK: - MKMapViewDelegate
extension MapViewController: MKMapViewDelegate {
// Add delegates here
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is AdventureMapOverlay {
return tileRenderer
} else {
return shimmerRenderer
}
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
switch annotation {
case let user as MKUserLocation:
if let existingView = mapView.dequeueReusableAnnotationView(withIdentifier: "user") {
return existingView
} else {
let view = MKAnnotationView(annotation: user, reuseIdentifier: "user")
// swiftlint:disable:next discouraged_object_literal
view.image = #imageLiteral(resourceName: "user")
return view
}
case let warp as WarpZone:
if let existingView = mapView.dequeueReusableAnnotationView(
withIdentifier: WarpAnnotationView.identifier) {
existingView.annotation = annotation
return existingView
} else {
return WarpAnnotationView(annotation: warp, reuseIdentifier: WarpAnnotationView.identifier)
}
default:
return nil
}
}
}
// MARK: - Game UI
extension MapViewController {
private func heartsString() -> String {
// swiftlint:disable:next identifier_name
guard let hp = Game.shared.adventurer?.hitPoints else { return "☠️" }
return String(repeating: "❤️", count: hp / 2)
}
private func goldString() -> String {
guard let gold = Game.shared.adventurer?.gold else { return "" }
return "\(gold)"
}
private func renderGame() {
heartsLabel.text = heartsString() + "\n" + goldString()
}
}
// MARK: - GameDelegate
extension MapViewController: GameDelegate {
func encounteredMonster(monster: Monster) {
showFight(monster: monster)
}
func showFight(monster: Monster, subtitle: String = "Fight?") {
let alertController = UIAlertController()
let runAction = UIAlertAction(title: "Run", style: .cancel) { _ in
self.showFight(monster: monster, subtitle: "I think you should really fight this.")
}
let fightAction = UIAlertAction(title: "Fight", style: .default) { _ in
guard let result = Game.shared.fight(monster: monster) else { return }
switch result {
case .heroLost:
print("loss!")
case .heroWon:
print("win!")
case .tie:
self.showFight(monster: monster, subtitle: "A good row, but you are both still in the fight!")
}
}
alertController.title = "A wild \(monster.name) appeared!"
alertController.addActions(actions: [runAction, fightAction])
present(alertController, animated: true)
}
func encounteredNPC(npc: NPC) {
let alertController = UIAlertController()
let noThanksAction = UIAlertAction(title: "No Thanks", style: .cancel) { _ in
print("done with encounter")
}
let onMyWayAction = UIAlertAction(title: "On My Way", style: .default) { _ in
print("did not buy anything")
}
alertController.title = npc.name
alertController.addActions(actions: [noThanksAction, onMyWayAction])
present(alertController, animated: true)
}
func enteredStore(store: Store) {
let alertController = UIAlertController()
let backOutAction = UIAlertAction(title: "Back Out", style: .cancel) { _ in
print("did not buy anything")
}
let takeMoneyAction = UIAlertAction(title: "Take My ", style: .default) { _ in
self.performSegue(withIdentifier: "shop", sender: store)
}
alertController.title = store.name
alertController.addActions(actions: [backOutAction, takeMoneyAction])
present(alertController, animated: true)
}
}
16. AdventureMapOverlay.swift
import Foundation
import MapKit
class AdventureMapOverlay: MKTileOverlay {
override func url(forTilePath path: MKTileOverlayPath) -> URL {
let tilePath = Bundle.main.url(
forResource: "\(path.y)",
withExtension: "png",
subdirectory: "tiles/\(path.z)/\(path.x)",
localization: nil)
if let tile = tilePath {
return tile
} else {
return Bundle.main.url(
forResource: "parchment",
withExtension: "png",
subdirectory: "tiles",
localization: nil)!
// swiftlint:disable:previous force_unwrapping
}
}
}
17. ShimmerRenderer.swift
import UIKit
import MapKit
class ShimmerRenderer: MKPolygonRenderer {
// MARK: - Properties
var iteration = 0
var locations: [CGFloat] = [0, 0, 0]
func updateLocations() {
iteration = (iteration + 1) % 15
let minL = max(0, CGFloat(iteration - 1) / 15.0)
let maxL = min(1.0, CGFloat(iteration + 1) / 15.0)
let center = CGFloat(iteration) / 15.0
locations = [minL, center, maxL]
}
// MARK: - Overridden
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
super.draw(mapRect, zoomScale: zoomScale, in: context)
UIGraphicsPushContext(context)
let boundingRect = path.boundingBoxOfPath
let minX = boundingRect.minX
let maxX = boundingRect.maxX
// swiftlint:disable:next discouraged_object_literal
let colors = [#colorLiteral(red: 0.2431372549, green: 0.5803921569, blue: 0.9764705882, alpha: 1).cgColor, #colorLiteral(red: 0.9999960065, green: 1, blue: 1, alpha: 0.8523706897).cgColor, #colorLiteral(red: 0.2431372549, green: 0.5803921569, blue: 0.9764705882, alpha: 1).cgColor]
let gradient = CGGradient(colorsSpace: nil, colors: colors as CFArray, locations: locations)
context.addPath(self.path)
context.clip()
context.drawLinearGradient(gradient!, start: CGPoint(x: minX, y: 0), end: CGPoint(x: maxX, y: 0), options: [])
// swiftlint:disable:previous force_unwrapping
UIGraphicsPopContext()
}
}
18. HeroViewController.swift
import UIKit
class HeroViewController: UIViewController {
// MARK: - IBOutlets
@IBOutlet weak var avatarImageView: UIImageView!
@IBOutlet weak var nameLabel: UILabel!
// MARK: - View Life Cycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// swiftlint:disable:next discouraged_object_literal
avatarImageView.image = #imageLiteral(resourceName: "adventurer")
}
}
// MARK: - UICollectionViewDataSource
extension HeroViewController: UICollectionViewDataSource {
var inventory: [Item] { return Game.shared.adventurer?.inventory ?? [] }
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return inventory.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
// swiftlint:disable force_cast
let imageView = cell.viewWithTag(1) as! UIImageView
let label = cell.viewWithTag(2) as! UILabel
// swiftlint:enable force_cast
let item = inventory[indexPath.row]
imageView.image = Game.shared.image(for: item)
if let weapon = item as? Weapon {
label.text = "+\(weapon.strength)"
}
cell.layer.cornerRadius = 8
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
return cell
}
}
19. ShopViewController.swift
import Foundation
import UIKit
class ShopViewController: UIViewController {
// MARK: - Properties
// swiftlint:disable:next implicitly_unwrapped_optional
var shop: Store!
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
title = shop?.name
}
}
// MARK: - UICollectionViewDataSource
extension ShopViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return shop.inventory.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
// swiftlint:disable force_cast
let imageView = cell.viewWithTag(1) as! UIImageView
let label = cell.viewWithTag(2) as! UILabel
// swiftlint:enable force_cast
let item = shop.inventory[indexPath.row]
imageView.image = Game.shared.image(for: item)
let price = item.cost
label.text = "\(price)"
cell.layer.cornerRadius = 8
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
return cell
}
}
// MARK: - UICollectionViewDelegate
extension ShopViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = shop.inventory[indexPath.row]
_ = Game.shared.purchaseItem(item: item)
_ = navigationController?.popViewController(animated: true)
}
}
20. WarpZone.swift
import MapKit
import UIKit
class WarpZone: NSObject, MKAnnotation {
// MARK: - Properties
let coordinate: CLLocationCoordinate2D
let color: UIColor
// MARK: - Initializers
init(latitude: CLLocationDegrees, longitude: CLLocationDegrees, color: UIColor) {
self.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
self.color = color
super.init()
}
}
extension WarpZone {
var image: UIImage {
// swiftlint:disable:next discouraged_object_literal
return #imageLiteral(resourceName: "warp").maskWithColor(color: self.color)
}
}
class WarpAnnotationView: MKAnnotationView {
static let identifier = "WarpZone"
override var annotation: MKAnnotation? {
get { super.annotation }
set {
super.annotation = newValue
guard let warp = newValue as? WarpZone else { return }
self.image = warp.image
}
}
}
extension UIImage {
func maskWithColor(color: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
// swiftlint:disable:next force_unwrapping
let context = UIGraphicsGetCurrentContext()!
color.setFill()
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: 1.0, y: -1.0)
let rect = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
// swiftlint:disable:next force_unwrapping
context.draw(cgImage!, in: rect)
context.setBlendMode(.sourceIn)
context.addRect(rect)
context.drawPath(using: .fill)
let coloredImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// swiftlint:disable:next force_unwrapping
return coloredImage!
}
}
后记
本篇主要讲述了自定义
MapKit Tiles
,感兴趣的给个赞或者关注~~~