现在我的应用程序显示一个customUserAnnotationView以及一个带有自定义图像的用户批注(您可以在ViewController.swift中看到)。我还创建了一个自定义UIView,希望将其用作用户注释上方的注释(其代码和图像位于SpeechBubble.swift下)。
我想结合这两个对象,以便可以显示CustomUserAnnotationView和上面的注释中放置的Custom UIView(SpeechBubble.swift)。
我尝试制作科学怪人程序from multiple mapbox tutorials的尝试对我来说没有成功。我只想将我创建的自定义注释类放在图像上方,并可能添加一个小三角形以使其看起来像气泡。
ViewController.swift
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.delegate = self
// Enable heading tracking mode so that the arrow will appear.
mapView.userTrackingMode = .followWithHeading
// Enable the permanent heading indicator, which will appear when the tracking mode is not `.followWithHeading`.
mapView.showsUserHeadingIndicator = true
view.addSubview(mapView)
let idea = UITextView(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
idea.text = "Hello There"
idea.textAlignment = NSTextAlignment.center
let sb = SpeechBubble(coord: mapView.targetCoordinate, idea: idea)
mapView.addSubview(sb)
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
// Substitute our custom view for the user location annotation. This custom view is defined below.
if annotation is MGLUserLocation && mapView.userLocation != nil {
return Avatar()
}
return nil
}
// Optional: tap the user location annotation to toggle heading tracking mode.
func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) {
if mapView.userTrackingMode != .followWithHeading {
mapView.userTrackingMode = .followWithHeading
} else {
mapView.resetNorth()
}
// We're borrowing this method as a gesture recognizer, so reset selection state.
mapView.deselectAnnotation(annotation, animated: false)
}
}
SpeechBubble.swift
import UIKit
import Mapbox
class SpeechBubble: UIView, MGLMapViewDelegate{
//var sbView: UIView
init(coord: CLLocationCoordinate2D, idea: UITextView) {
let width = CGFloat(180)
let height = UITextField.layoutFittingExpandedSize.height + 32
super.init(frame: CGRect(x: CGFloat(coord.latitude), y: CGFloat(coord.longitude), width: width, height: height))
self.addSubview(idea)
self.addSubview(buttonsView());
self.addSubview(upvoteView());
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func upvoteView() -> UIView {
let uView = UIView()
let vCnt = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
vCnt.center = CGPoint(x: 10.5, y: 32)
vCnt.textAlignment = .center
vCnt.text = "0"
let uButton = UIButton(type: .custom)
uButton.frame = CGRect(x: vCnt.frame.size.width + 5, y: 0, width: 32, height: 32);
let uImage = UIImage (named: "Upvote")
uButton.setImage(uImage, for: .normal)
uView.frame.size.width = vCnt.frame.size.width + uButton.frame.size.width + 5
uView.frame.size.height = max(vCnt.frame.size.height, uButton.frame.size.height)
uView.frame = CGRect(
x: 0,
y: self.frame.size.height - uView.frame.size.height,
width: uView.frame.size.width,
height: uView.frame.size.height );
uView.addSubview(vCnt)
uView.addSubview(uButton)
return uView
}
func buttonsView() -> UIView {
let bView = UIView()
let jButton = UIButton(type: .custom)
rButton.frame = CGRect(x: 0, y: 0, width: 35, height: 32);
let rImage = UIImage (named: "Rocket")
rButton.setImage(rImage, for: .normal)
let pButton = UIButton(type: .custom)
pButton.frame = CGRect(x: jButton.frame.size.width + 5, y: 0, width: 31, height: 36);
let pImage = UIImage (named: "Profile")
pButton.setImage(pImage, for: .normal)
bView.frame.size.width = rButton.frame.size.width + pButton.frame.size.width + 5
bView.frame.size.height = max(rButton.frame.size.height, pButton.frame.size.height)
bView.frame = CGRect(
x: self.frame.size.width - bView.frame.size.width,
y: self.frame.size.height - bView.frame.size.height,
width: bView.frame.size.width,
height: bView.frame.size.height );
bView.addSubview(rButton)
bView.addSubview(pButton)
return bView
}
}
Avatar.swift
import Mapbox
class Avatar: MGLUserLocationAnnotationView {
let size: CGFloat = 48
var arrow: CALayer!
//var arrow: CAShapeLayer!
// -update is a method inherited from MGLUserLocationAnnotationView. It updates the appearance of the user location annotation when needed. This can be called many times a second, so be careful to keep it lightweight.
override func update() {
if frame.isNull {
frame = CGRect(x: 0, y: 0, width: size, height: size)
return setNeedsLayout()
}
// Check whether we have the user’s location yet.
if CLLocationCoordinate2DIsValid(userLocation!.coordinate) {
setupLayers()
updateHeading()
}
}
private func updateHeading() {
// Show the heading arrow, if the heading of the user is available.
if let heading = userLocation!.heading?.trueHeading {
arrow.isHidden = false
// Get the difference between the map’s current direction and the user’s heading, then convert it from degrees to radians.
let rotation: CGFloat = -MGLRadiansFromDegrees(mapView!.direction - heading)
// If the difference would be perceptible, rotate the arrow.
if abs(rotation) > 0.01 {
// Disable implicit animations of this rotation, which reduces lag between changes.
CATransaction.begin()
CATransaction.setDisableActions(true)
arrow.setAffineTransform(CGAffineTransform.identity.rotated(by: rotation))
CATransaction.commit()
}
} else {
arrow.isHidden = true
}
}
private func setupLayers() {
// This dot forms the base of the annotation.
if arrow == nil {
arrow = CALayer()
let myImage = UIImage(named: "will_smith")?.cgImage
arrow.bounds = CGRect(x: 0, y: 0, width: size, height: size)
arrow.contents = myImage
layer.addSublayer(arrow)
}
}
// Calculate the vector path for an arrow, for use in a shape layer.
private func arrowPath() -> CGPath {
let max: CGFloat = size / 2
let pad: CGFloat = 3
let top = CGPoint(x: max * 0.5, y: 0)
let left = CGPoint(x: 0 + pad, y: max - pad)
let right = CGPoint(x: max - pad, y: max - pad)
let center = CGPoint(x: max * 0.5, y: max * 0.6)
let bezierPath = UIBezierPath()
bezierPath.move(to: top)
bezierPath.addLine(to: left)
bezierPath.addLine(to: center)
bezierPath.addLine(to: right)
bezierPath.addLine(to: top)
bezierPath.close()
return bezierPath.cgPath
}
}
更新
我试图用Answer和我的代码创建一个Frankenstein程序,并在SpeechBubble.swift中收到以下错误Property 'self.representedObject' not initialized at super.init call
。我还将所有旧代码从SpeechBubble.swift移入insideSpeechBubble.swift
更新了SpeechBubble.swift
import UIKit
import Mapbox
class SpeechBubble: UIView, MGLCalloutView {
// Your IBOutlets //
var representedObject: MGLAnnotation
var annotationPoint: CGPoint
// Required views but unused for this implementation.
lazy var leftAccessoryView = UIView()
lazy var rightAccessoryView = UIView()
var contentView: MGLMapView
weak var delegate: MGLCalloutViewDelegate?
// MARK: - init methods
required init(annotation: MGLAnnotation, frame: CGRect, annotationPoint: CGPoint) {
let idea = UITextView(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
idea.text = "Hello There"
idea.textAlignment = NSTextAlignment.center
self.representedObject = annotation
self.annotationPoint = annotationPoint
contentView = InsideSpeechBubble(coord: annotationPoint, idea: idea )
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("SpeechBubble", owner: self, options: nil)
addSubview(contentView as UIView)
contentView.frame = self.bounds
// Do your initialisation //
}
// MARK: - MGLCalloutView methods
func presentCallout(from rect: CGRect, in view: UIView, constrainedTo constrainedRect: CGRect, animated: Bool) {
// Present the custom callout slightly above the annotation's view. Initially invisble.
self.center = annotationPoint.applying(CGAffineTransform(translationX: 0, y: -self.frame.height - 20.0))
// I have logic here for setting the correct image and button states //
}
func dismissCallout(animated: Bool) {
removeFromSuperview()
}
}
更新了ViewController.swift
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate {
//let point = MGLPointAnnotation()
override func viewDidLoad() {
super.viewDidLoad()
let mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.delegate = self
// Enable heading tracking mode so that the arrow will appear.
mapView.userTrackingMode = .followWithHeading
// Enable the permanent heading indicator, which will appear when the tracking mode is not `.followWithHeading`.
mapView.showsUserHeadingIndicator = true
view.addSubview(mapView)
let HighDea = UITextView(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
HighDea.text = "Hello There"
HighDea.textAlignment = NSTextAlignment.center
//let sb = SpeechBubble()
//mapView.addSubview(sb)
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
// Substitute our custom view for the user location annotation. This custom view is defined below.
if annotation is MGLUserLocation && mapView.userLocation != nil {
return Avatar()
}
return nil
}
func mapView(_ mapView: MGLMapView, calloutViewFor annotation: MGLAnnotation) -> MGLCalloutView? {
// Do your annotation-specific preparation here //
// I get the correct size from my xib file.
let viewFrame = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 261.0, height: 168.0))
// Get the annotation's location in the view's coordinate system.
let annotationPoint = mapView.convert(annotation.coordinate, toPointTo: nil)
let customCalloutView = SpeechBubble(annotation: annotation, frame: viewFrame, annotationPoint: annotationPoint)
return customCalloutView
}
// func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
// This example is only concerned with point annotations.
// guard annotation is MGLPointAnnotation else {
// return nil
// }
// Use the point annotation’s longitude value (as a string) as the reuse identifier for its view.
// let reuseIdentifier = "\(annotation.coordinate.longitude)"
// For better performance, always try to reuse existing annotations.
// var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
// If there’s no reusable annotation view available, initialize a new one.
// if annotationView == nil {
// annotationView = CustomAnnotationView(reuseIdentifier: reuseIdentifier)
// annotationView!.bounds = CGRect(x: 0, y: 0, width: 40, height: 40)
// Set the annotation view’s background color to a value determined by its longitude.
// let hue = CGFloat(annotation.coordinate.longitude) / 100
// annotationView!.backgroundColor = UIColor(hue: hue, saturation: 0.5, brightness: 1, alpha: 1)
// }
// return annotationView
// }
// Optional: tap the user location annotation to toggle heading tracking mode.
func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) {
if mapView.userTrackingMode != .followWithHeading {
mapView.userTrackingMode = .followWithHeading
} else {
mapView.resetNorth()
}
// We're borrowing this method as a gesture recognizer, so reset selection state.
mapView.deselectAnnotation(annotation, animated: false)
}
}
答案 0 :(得分:3)
当我为Mapbox注释实现自定义标注时,我使用了xib文件来设计实际的标注。我发现,与尝试从代码中构造UI相比,它给了我更多的即时反馈(但显然可以随您的喜好进行操作)。
这给了我以下类似的东西。
使用UIImage作为背景,可以实现我选择的任何形状。在这里,我使用白色周围的透明度为您提供问题中提到的圆形元素和底部三角形。
此UIView的Swift文件(您的SpeechBubble)需要符合MGLCalloutView
协议,而不是MGLMapViewDelegate
协议。您的ViewController
是MGLMapViewDelegate
,而不是您的自定义标注。在IB中的Identity Inspector中以通常的方式将xib文件和Swift文件配对。所以会是这样的:
import UIKit
import Mapbox
class SpeechBubble: UIView, MGLCalloutView {
// Your IBOutlets //
@IBOutlet var contentView: UIView! // The custom callout's view.
var representedObject: MGLAnnotation
var annotationPoint: CGPoint
// Required views but unused for this implementation.
lazy var leftAccessoryView = UIView()
lazy var rightAccessoryView = UIView()
weak var delegate: MGLCalloutViewDelegate?
// MARK: - init methods
required init(annotation: YourAnnotation, frame: CGRect, annotationPoint: CGPoint) {
self.representedObject = annotation
self.annotationPoint = annotationPoint
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("SpeechBubble", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
// Do your initialisation //
}
// MARK: - MGLCalloutView methods
func presentCallout(from rect: CGRect, in view: UIView, constrainedTo constrainedRect: CGRect, animated: Bool) {
// Present the custom callout slightly above the annotation's view. Initially invisble.
self.center = annotationPoint.applying(CGAffineTransform(translationX: 0, y: -self.frame.height - 20.0))
// I have logic here for setting the correct image and button states //
}
func dismissCallout(animated: Bool) {
removeFromSuperview()
}
然后,您似乎只是缺少MGLMapViewDelegate
方法,无法在请求时实际返回SpeechBubble
视图。它应该在您的ViewController
文件中。
func mapView(_ mapView: MGLMapView, calloutViewFor annotation: MGLAnnotation) -> MGLCalloutView? {
// Do your annotation-specific preparation here //
// I get the correct size from my xib file.
let viewFrame = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 261.0, height: 168.0))
// Get the annotation's location in the view's coordinate system.
let annotationPoint = mapView.convert(annotation.coordinate, toPointTo: nil)
let customCalloutView = SpeechBubble(annotation: YourAnnotation, frame: viewFrame, annotationPoint: annotationPoint)
return customCalloutView
}
希望这将使您更接近实现自己的目标。顺便说一句,这个问题的版本比第一个要领先几英里。
编辑++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++
在没有看到您的项目的情况下几乎不可能完成这项工作,因此我整理了一个基本的实现。它基于此处的Mapbox示例:Mapbox Custom Callout,由于某种原因,该示例未显示如何实际提供标注视图。我还扩展了它以允许自定义注释图像。如果可以进行此工作,则应该可以将相关部分移到您自己的项目中。
我强烈建议,如果您尝试实施以下内容,请在一个新项目中进行。
视图控制器。
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let mapView = MGLMapView(frame: view.bounds, styleURL: MGLStyle.lightStyleURL)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.tintColor = .darkGray
view.addSubview(mapView)
// Set the map view‘s delegate property.
mapView.delegate = self
// Initialize and add the marker annotation.
let coordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0)
let marker = MyAnnotation(coordinate: coordinate, title: "Bingo", subtitle: "Bongo")
// Add marker to the map.
mapView.addAnnotation(marker)
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
func mapView(_ mapView: MGLMapView, calloutViewFor annotation: MGLAnnotation) -> MGLCalloutView? {
// Instantiate and return our custom callout view.
let annotationPoint = mapView.convert(annotation.coordinate, toPointTo: nil)
let viewFrame = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 250.0, height: 178.0))
return CustomCalloutView(representedObject: annotation, frame: viewFrame, annotationPoint: annotationPoint)
}
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "myAnnotationView") {
return annotationView
} else {
let annotationView = MyAnnotationView(reuseIdentifier: "myAnnotationView", size: CGSize(width: 45, height: 45), annotation: annotation)
return annotationView
}
}
func mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {
// Optionally handle taps on the callout.
print("Tapped the callout for: \(annotation)")
// Hide the callout.
mapView.deselectAnnotation(annotation, animated: true)
}
}
CustomCalloutView.swift
import UIKit
import Mapbox
class CustomCalloutView: UIView, MGLCalloutView {
@IBOutlet var contentView: UIView!
weak var delegate: MGLCalloutViewDelegate?
var representedObject: MGLAnnotation
var annotationPoint: CGPoint
// Required views but unused for this implementation.
lazy var leftAccessoryView = UIView()
lazy var rightAccessoryView = UIView()
required init(representedObject: MGLAnnotation, frame: CGRect, annotationPoint: CGPoint) {
self.representedObject = representedObject
self.annotationPoint = annotationPoint
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
let coordinate = CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)
self.representedObject = MyAnnotation(coordinate: coordinate, title: "", subtitle: "")
self.annotationPoint = CGPoint(x: 50.0, y: 50.0)
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
Bundle.main.loadNibNamed("CustomCalloutView", owner: self, options: nil)
addSubview(contentView)
}
func presentCallout(from rect: CGRect, in view: UIView, constrainedTo constrainedRect: CGRect, animated: Bool) {
// Present the custom callout slightly above the annotation's view. Initially invisble.
self.center = annotationPoint.applying(CGAffineTransform(translationX: 0.0, y: -120.0))
view.addSubview(self)
}
func dismissCallout(animated: Bool) {
removeFromSuperview()
}
}
这与xib文件相关联/标识。现在它只包含一个简单的图像形状。我必须(重新)引入contentView IBOutlet,因为我无法从Bundle中加载内容并将其添加到commonInit()中的self中使一切变得很快乐。
自定义注释类。
import UIKit
import Mapbox
// MGLAnnotation protocol reimplementation
class MyAnnotation: NSObject, MGLAnnotation {
// As a reimplementation of the MGLAnnotation protocol, we have to add mutable coordinate and (sub)title properties ourselves.
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
// Custom properties that we will use to customize the annotation.
var image: UIImage?
var reuseIdentifier: String?
init(coordinate: CLLocationCoordinate2D, title: String?, subtitle: String?) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
self.reuseIdentifier = "myAnnotation"
}
}
MGLAnnotationView子类。
import UIKit
import Mapbox
class MyAnnotationView: MGLAnnotationView {
init(reuseIdentifier: String, size: CGSize, annotation: MGLAnnotation) {
super.init(reuseIdentifier: reuseIdentifier)
// This property prevents the annotation from changing size when the map is tilted.
scalesWithViewingDistance = false
// Begin setting up the view.
frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
let imageView = UIImageView(frame: frame)
var image = UIImage()
if annotation is MyAnnotation {
image = UIImage(named: "frog")!
}
imageView.image = image
addSubview(imageView)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
自然,有很多硬编码数字,并且需要一个名为frog的图像,但是您可以更改所有这些图像并根据需要对其进行改进。需要在身份检查器等中以通常的方式链接CustomCalloutView.swift和CustomCalloutView.xib。