版本记录
版本号 | 时间 |
---|---|
V1.0 | 2019.04.25 星期四 |
前言
MapKit框架直接从您的应用界面显示地图或卫星图像,调出兴趣点,并确定地图坐标的地标信息。接下来几篇我们就一起看一下这个框架。感兴趣的看下面几篇文章。
1. MapKit框架详细解析(一) —— 基本概览(一)
2. MapKit框架详细解析(二) —— 基本使用简单示例(一)
3. MapKit框架详细解析(三) —— 基本使用简单示例(二)
4. MapKit框架详细解析(四) —— 一个叠加视图相关的简单示例(一)
5. MapKit框架详细解析(五) —— 一个叠加视图相关的简单示例(二)
6. MapKit框架详细解析(六) —— 添加自定义图块(一)
7. MapKit框架详细解析(七) —— 添加自定义图块(二)
源码
1. Swift
首先看下文档组织结构
接着看下sb文件
下面就是源码了
1. AppDelegate.swift
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let locationListener = LocationListener()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Game.shared.adventurer = Adventurer(name: "Hero", hitPoints: 10, strength: 10, gold: 40)
return true
}
}
2. 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)
}
}
3. MapViewController.swift
import UIKit
import MapKit
class MapViewController: UIViewController {
// MARK: - IBOutlets
@IBOutlet weak var mapView: MKMapView!
@IBOutlet weak var heartsLabel: UILabel!
// MARK: - Properties
var tileRenderer: MKTileOverlayRenderer!
var shimmerRenderer: ShimmerRenderer!
// 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.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)
}
func setupTileRenderer() {
let overlay = AdventureMapOverlay()
overlay.canReplaceMapContent = true
mapView.add(overlay, level: MKOverlayLevel.aboveLabels)
tileRenderer = MKTileOverlayRenderer(tileOverlay: overlay)
overlay.minimumZ = 13
overlay.maximumZ = 16
}
func setupLakeOverlay() {
// 1
let lake = MKPolygon(coordinates: &Game.shared.reservoir, count: Game.shared.reservoir.count)
mapView.add(lake)
// 2
shimmerRenderer = ShimmerRenderer(overlay: lake)
shimmerRenderer.fillColor = #colorLiteral(red: 0.2431372549, green: 0.5803921569, blue: 0.9764705882, alpha: 1)
// 3
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
self?.shimmerRenderer.updateLocations()
self?.shimmerRenderer.setNeedsDisplay()
}
}
@objc func gameUpdated(notification: Notification) {
renderGame()
}
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
}
}
}
// MARK: - MapView Delegate
extension MapViewController: MKMapViewDelegate {
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:
let view = mapView.dequeueReusableAnnotationView(withIdentifier: "user")
?? MKAnnotationView(annotation: user, reuseIdentifier: "user")
view.image = #imageLiteral(resourceName: "user")
return view
case let warp as WarpZone:
let view = mapView.dequeueReusableAnnotationView(withIdentifier: WarpAnnotationView.identifier)
?? WarpAnnotationView(annotation: warp, reuseIdentifier: WarpAnnotationView.identifier)
view.annotation = warp
return view
default:
return nil
}
}
}
// MARK: - Game UI
extension MapViewController {
private func heartsString() -> String {
guard let hp = Game.shared.adventurer?.hitPoints else { return "☠️" }
let heartCount = hp / 2
var string = ""
for _ in 1 ... heartCount {
string += "❤️"
}
return string
}
private func goldString() -> String {
guard let gold = Game.shared.adventurer?.gold else { return "" }
return "💰\(gold)"
}
fileprivate func renderGame() {
heartsLabel.text = heartsString() + "\n" + goldString()
}
}
// MARK: - Game Delegate
extension MapViewController: GameDelegate {
func encounteredMonster(monster: Monster) {
showFight(monster: monster)
}
func showFight(monster: Monster, subtitle: String = "Fight?") {
let alert = AABlurAlertController()
alert.addAction(action: AABlurAlertAction(title: "Run", style: .cancel) { [unowned self] _ in
self.showFight(monster: monster, subtitle: "I think you should really fight this.")
})
alert.addAction(action: AABlurAlertAction(title: "Fight", style: .default) { [unowned self] _ 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!")
}
})
alert.blurEffectStyle = .regular
let image = Game.shared.image(for: monster)
alert.alertImage.image = image
alert.alertTitle.text = "A wild \(monster.name) appeared!"
alert.alertSubtitle.text = subtitle
present(alert, animated: true)
}
func encounteredNPC(npc: NPC) {
let alert = AABlurAlertController()
alert.addAction(action: AABlurAlertAction(title: "No Thanks", style: .cancel) { _ in
print("done with encounter")
})
alert.addAction(action: AABlurAlertAction(title: "On My Way", style: .default) { _ in
print("did not buy anything")
})
alert.blurEffectStyle = .regular
let image = Game.shared.image(for: npc)
alert.alertImage.image = image
alert.alertTitle.text = npc.name
alert.alertSubtitle.text = npc.quest
present(alert, animated: true)
}
func enteredStore(store: Store) {
let alert = AABlurAlertController()
alert.addAction(action: AABlurAlertAction(title: "Back Out", style: .cancel) { _ in
print("did not buy anything")
})
alert.addAction(action: AABlurAlertAction(title: "Take My 💰", style: .default) { [unowned self] _ in
self.performSegue(withIdentifier: "shop", sender: store)
})
alert.blurEffectStyle = .regular
let image = Game.shared.image(for: store)
alert.alertImage.image = image
alert.alertTitle.text = store.name
alert.alertSubtitle.text = "Shopping for accessories?"
present(alert, animated: true)
}
}
4. 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)
guard let tile = tilePath else {
return Bundle.main.url(
forResource: "parchment",
withExtension: "png",
subdirectory: "tiles",
localization: nil)!
}
return tile
}
}
5. 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 = self.path.boundingBoxOfPath
let minX = boundingRect.minX
let maxX = boundingRect.maxX
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: [])
UIGraphicsPopContext()
}
}
6. 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)
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)
let imageView = cell.viewWithTag(1) as! UIImageView
let label = cell.viewWithTag(2) as! UILabel
let item = inventory[indexPath.row]
imageView.image = Game.shared.image(for: item)
label.text = ""
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
}
}
7. ShopViewController.swift
import Foundation
import UIKit
class ShopViewController: UIViewController {
// MARK: - Properties
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)
let imageView = cell.viewWithTag(1) as! UIImageView
let label = cell.viewWithTag(2) as! UILabel
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)
}
}
8. PointOfInterest+MapKit.swift
import Foundation
import MapKit
extension PointOfInterest: MKAnnotation {
var coordinate: CLLocationCoordinate2D { return location.coordinate }
var title: String? { return name }
}
9. 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 {
return #imageLiteral(resourceName: "warp").maskWithColor(color: self.color)
}
}
class WarpAnnotationView: MKAnnotationView {
static let identifier = "WarpZone"
override var annotation: MKAnnotation? {
get { return 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)
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)
context.draw(cgImage!, in: rect)
context.setBlendMode(.sourceIn)
context.addRect(rect)
context.drawPath(using: .fill)
let coloredImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return coloredImage!
}
}
10. Game.swift
import UIKit
import CoreLocation
let ENCOUNTER_RADIUS: 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]
}
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))
]
}
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 < ENCOUNTER_RADIUS {
//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
}
}
}
11. 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)
}
12. 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")
}
13. Adventurer.swift
import Foundation
class Adventurer: Monster {
// MARK: - Properties
var isDefeated = false
var maxHitPoints: Int = 0
var inventory: [Item] = []
override var strength: Int {
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
}
}
14. 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
}
}
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)
}
15. Encounter.swift
import Foundation
protocol Encounter {
}
extension Monster: Encounter {
}
extension Store: Encounter {
}
16. 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])
}
17. 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
}
}
18. 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)
}
19. AABlurAlertController.swift
import UIKit
public enum AABlurActionStyle {
case `default`, cancel
}
open class AABlurAlertAction: UIButton {
fileprivate var handler: ((AABlurAlertAction) -> Void)? = nil
fileprivate var style: AABlurActionStyle = AABlurActionStyle.default
fileprivate var parent: AABlurAlertController? = nil
public init(title: String?, style: AABlurActionStyle, handler: ((AABlurAlertAction) -> Void)?) {
super.init(frame: CGRect.zero)
self.style = style
self.handler = handler
self.addTarget(self, action: #selector(buttonTapped), for: UIControlEvents.touchUpInside)
self.setTitle(title, for: UIControlState.normal)
switch self.style {
case .cancel:
self.setTitleColor(UIColor(red:0.47, green:0.50, blue:0.55, alpha:1.00), for: UIControlState.normal)
self.backgroundColor = UIColor(red:0.93, green:0.94, blue:0.95, alpha:1.00)
self.layer.borderColor = UIColor(red:0.74, green:0.77, blue:0.79, alpha:1.00).cgColor
default:
self.setTitleColor(UIColor.white, for: UIControlState.normal)
self.backgroundColor = UIColor(red:0.31, green:0.57, blue:0.87, alpha:1.00)
self.layer.borderColor = UIColor(red:0.17, green:0.38, blue:0.64, alpha:1.00).cgColor
}
self.setTitleColor(self.titleColor(for: UIControlState.normal)?.withAlphaComponent(0.5), for: UIControlState.highlighted)
self.layer.borderWidth = 1
self.layer.cornerRadius = 5
self.layer.shadowOffset = CGSize(width: 0, height: 2)
self.layer.shadowRadius = 4
self.layer.shadowOpacity = 0.1
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
@objc fileprivate func buttonTapped(_ sender: AABlurAlertAction) {
self.parent?.dismiss(animated: true, completion: {
self.handler?(sender)
})
}
}
open class AABlurAlertController: UIViewController {
open var blurEffectStyle: UIBlurEffectStyle = .light
open var imageHeight: Float = 175
open var alertViewWidth: Float?
/**
Set the max alert view width
If you don't want to have a max width set this to nil.
It will take 70% of the superview width by default
Default : 450
*/
open var maxAlertViewWidth: CGFloat? = 450
fileprivate var backgroundImage : UIImageView = UIImageView()
fileprivate var alertView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor(red:0.98, green:0.98, blue:0.98, alpha:1.00)
view.layer.cornerRadius = 5
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 15)
view.layer.shadowRadius = 12
view.layer.shadowOpacity = 0.22
return view
}()
open var alertImage : UIImageView = {
let imgView = UIImageView()
imgView.translatesAutoresizingMaskIntoConstraints = false
imgView.contentMode = .scaleAspectFit
return imgView
}()
open let alertTitle : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.font = UIFont.boldSystemFont(ofSize: 17)
lbl.textColor = UIColor(red:0.20, green:0.22, blue:0.26, alpha:1.00)
lbl.textAlignment = .center
return lbl
}()
open let alertSubtitle : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.font = UIFont.boldSystemFont(ofSize: 14)
lbl.textColor = UIColor(red:0.51, green:0.54, blue:0.58, alpha:1.00)
lbl.textAlignment = .center
lbl.numberOfLines = 0
return lbl
}()
fileprivate let buttonsStackView : UIStackView = {
let sv = UIStackView()
sv.translatesAutoresizingMaskIntoConstraints = false
sv.distribution = .fillEqually
sv.spacing = 16
return sv
}()
public init() {
super.init(nibName: nil, bundle: nil)
self.modalTransitionStyle = .crossDissolve
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func setup() {
// Clean the views
self.view.subviews.forEach{ $0.removeFromSuperview() }
self.backgroundImage.subviews.forEach{ $0.removeFromSuperview() }
// Set up view
self.view.frame = UIScreen.main.bounds
self.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
self.view.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
// Set up background image
self.backgroundImage.frame = self.view.bounds
self.backgroundImage.autoresizingMask = [.flexibleHeight, .flexibleWidth]
self.view.addSubview(backgroundImage)
// Set up the alert view
self.view.addSubview(alertView)
// Set up alertImage
self.alertView.addSubview(alertImage)
// Set up alertTitle
self.alertView.addSubview(alertTitle)
// Set up alertSubtitle
self.alertView.addSubview(alertSubtitle)
// Set up buttonsStackView
self.alertView.addSubview(buttonsStackView)
// Set up background Tap
if buttonsStackView.arrangedSubviews.count <= 0 {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapOnBackground))
self.backgroundImage.isUserInteractionEnabled = true
self.backgroundImage.addGestureRecognizer(tapGesture)
}
setupConstraints()
}
fileprivate func setupConstraints() {
let viewsDict: [String: Any] = [
"alertView": alertView,
"alertImage": alertImage,
"alertTitle": alertTitle,
"alertSubtitle": alertSubtitle,
"buttonsStackView": buttonsStackView
]
let spacing = 14
let viewMetrics: [String: Any] = [
"margin": spacing * 2,
"buttonMargin": 10,
"spacing": spacing,
"alertViewWidth": 450,
"alertImageHeight": (alertImage.image != nil) ? imageHeight : 0,
"alertTitleHeight": 22,
"buttonsStackViewHeight": (buttonsStackView.arrangedSubviews.count > 0) ? 40 : 0
]
if let alertViewWidth = alertViewWidth {
self.view.addConstraints(NSLayoutConstraint.constraints(
withVisualFormat: "H:[alertView(alertViewWidth)]", options: [],
metrics: ["alertViewWidth":alertViewWidth], views: viewsDict))
} else {
let widthConstraints = NSLayoutConstraint(item: alertView,
attribute: NSLayoutAttribute.width,
relatedBy: NSLayoutRelation.equal,
toItem: self.view,
attribute: NSLayoutAttribute.width,
multiplier: 0.7, constant: 0)
if let maxAlertViewWidth = maxAlertViewWidth {
widthConstraints.priority = UILayoutPriority(rawValue: 999)
self.view.addConstraint(NSLayoutConstraint(
item: alertView,
attribute: NSLayoutAttribute.width,
relatedBy: NSLayoutRelation.lessThanOrEqual,
toItem: nil,
attribute: NSLayoutAttribute.width,
multiplier: 1,
constant: maxAlertViewWidth))
}
self.view.addConstraint(widthConstraints)
}
let alertSubtitleVconstraint = (alertSubtitle.text != nil) ? "spacing-[alertSubtitle]-" : ""
[NSLayoutConstraint(item: alertView, attribute: .centerX, relatedBy: .equal,
toItem: view, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: alertView, attribute: .centerY, relatedBy: .equal,
toItem: view, attribute: .centerY, multiplier: 1, constant: 0)
].forEach { self.view.addConstraint($0)}
[NSLayoutConstraint.constraints(withVisualFormat: "V:|-margin-[alertImage(alertImageHeight)]-spacing-[alertTitle(alertTitleHeight)]-\(alertSubtitleVconstraint)margin-[buttonsStackView(buttonsStackViewHeight)]-margin-|",
options: [], metrics: viewMetrics, views: viewsDict),
NSLayoutConstraint.constraints(withVisualFormat: "H:|-margin-[alertImage]-margin-|",
options: [], metrics: viewMetrics, views: viewsDict),
NSLayoutConstraint.constraints(withVisualFormat: "H:|-margin-[alertTitle]-margin-|",
options: [], metrics: viewMetrics, views: viewsDict),
NSLayoutConstraint.constraints(withVisualFormat: "H:|-margin-[alertSubtitle]-margin-|",
options: [], metrics: viewMetrics, views: viewsDict),
NSLayoutConstraint.constraints(withVisualFormat: "H:|-buttonMargin-[buttonsStackView]-buttonMargin-|",
options: [], metrics: viewMetrics, views: viewsDict)
].forEach { NSLayoutConstraint.activate($0) }
}
open override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setup()
// Set up blur effect
backgroundImage.image = snapshot()
let blurEffect = UIBlurEffect(style: blurEffectStyle)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = backgroundImage.bounds
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect)
vibrancyEffectView.frame = backgroundImage.bounds
vibrancyEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
blurEffectView.contentView.addSubview(vibrancyEffectView)
backgroundImage.addSubview(blurEffectView)
}
open func addAction(action: AABlurAlertAction) {
action.parent = self
buttonsStackView.addArrangedSubview(action)
}
fileprivate func snapshot() -> UIImage? {
guard let window = UIApplication.shared.keyWindow else { return nil }
UIGraphicsBeginImageContextWithOptions(window.bounds.size, false, window.screen.scale)
window.drawHierarchy(in: window.bounds, afterScreenUpdates: false)
let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return snapshotImage
}
@objc func tapOnBackground(sender: UITapGestureRecognizer) {
if sender.state == .ended {
self.dismiss(animated: true, completion: nil)
}
}
}
后记
本篇主要介绍了添加自定义图块,感兴趣的给个赞或者关注~~~