我想自定义iOS8 MapView Callout气泡,点击MKAnnotationView时可以看到它。默认气泡有点限制(只有标题,副标题和2个附件视图)所以我很难找到替代解决方案。这里有两种可能的方法和我面临的相关问题:
问题1)创建一个自定义的调用气泡
挖掘Apple documentation我发现了这个:
当您使用自定义视图而不是标准标注时,您需要 做额外的工作,以确保您的标注显示和隐藏适当 当用户与之互动时。以下步骤概述了该过程 创建包含按钮的自定义标注:
设计表示自定义的NSView或UIView子类 大喊。子类可能需要实现 drawRect:绘制自定义内容的方法。创建一个视图控制器 初始化callout视图并执行与之相关的操作 按钮。在注释视图中,实现hitTest:来响应 在注释视图的边界之外但在其中的命中 callout view的边界,如清单6-7所示。在注释中 查看,实现setSelected:animated:将您的标注视图添加为 用户单击或点击它时的注释视图的子视图。如果 当用户选择它时,callout视图已经可见 setSelected:方法应该从中删除callout子视图 注释视图(参见清单6-8)。在注释视图中 initWithAnnotation:方法,将canShowCallout属性设置为NO to 防止地图在用户显示标准标注时 选择注释。清单6-7显示了一个实现示例 hitTest:处理标注视图中可能位于标注之外的命中 注释视图的边界。
Listing 6-7 Responding to hits within a custom callout
- (NSView *)hitTest:(NSPoint)point
{
NSView *hitView = [super hitTest:point];
if (hitView == nil && self.selected) {
NSPoint pointInAnnotationView = [self.superview convertPoint:point toView:self];
NSView *calloutView = self.calloutViewController.view;
hitView = [calloutView hitTest:pointInAnnotationView];
}
return hitView;
}
清单6-8显示了实现setSelected:animated:to的示例 动画到达和解除自定义标注视图时 用户选择注释视图。
Listing 6-8 Adding and removing a custom callout view
- (void)setSelected:(BOOL)selected
{
[super setSelected:selected];
// Get the custom callout view.
NSView *calloutView = self.calloutViewController.view;
if (selected) {
NSRect annotationViewBounds = self.bounds;
NSRect calloutViewFrame = calloutView.frame;
// Center the callout view above and to the right of the annotation view.
calloutViewFrame.origin.x = -(NSWidth(calloutViewFrame) - NSWidth(annotationViewBounds)) * 0.5;
calloutViewFrame.origin.y = -NSHeight(calloutViewFrame) + 15.0;
calloutView.frame = calloutViewFrame;
[self addSubview:calloutView];
} else {
[calloutView.animator removeFromSuperview];
}
}
现在,当我尝试将此Objective-C代码转换为Swift时,我找不到此属性:
NSView *calloutView = self.calloutViewController.view;
如何访问标注气泡视图?
问题2)修改默认的播放气泡
如前所述,显示的默认标注有标题,副标题和2个附件视图。我注意到我无法改变字符串的字体样式或泡泡的颜色。此外,如果我的标题有超过24个字符我的配件视图定位搞砸了。 我该如何避免这个问题?
答案 0 :(得分:19)
calloutViewController是自定义标注视图的一部分,用于处理事件。你在MapKit或其他地方找不到它 苹果的说明很好。要创建自己的标注,您应该按照以下步骤操作:
1. Create custom MKAnnotationView or MAPinAnnotationView
2. Override setSelected and hitTest methods in your annotation
3. Create your own callout view
4. Override hitTest and pointInside in you callout view
5. Implement MapView delegate methods didSelectAnnotationView, didDeselectAnnotationView
我最终得到了这些解决方案,它允许我在标注视图中处理触摸而不会丢失选择。
class MapPin: MKAnnotationView {
class var reuseIdentifier:String {
return "mapPin"
}
private var calloutView:MapPinCallout?
private var hitOutside:Bool = true
var preventDeselection:Bool {
return !hitOutside
}
convenience init(annotation:MKAnnotation!) {
self.init(annotation: annotation, reuseIdentifier: MapPin.reuseIdentifier)
canShowCallout = false;
}
override func setSelected(selected: Bool, animated: Bool) {
let calloutViewAdded = calloutView?.superview != nil
if (selected || !selected && hitOutside) {
super.setSelected(selected, animated: animated)
}
self.superview?.bringSubviewToFront(self)
if (calloutView == nil) {
calloutView = MapPinCallout()
}
if (self.selected && !calloutViewAdded) {
addSubview(calloutView!)
}
if (!self.selected) {
calloutView?.removeFromSuperview()
}
}
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
var hitView = super.hitTest(point, withEvent: event)
if let callout = calloutView {
if (hitView == nil && self.selected) {
hitView = callout.hitTest(point, withEvent: event)
}
}
hitOutside = hitView == nil
return hitView;
}
}
class MapPinCallout: UIView {
override func hitTest(var point: CGPoint, withEvent event: UIEvent?) -> UIView? {
let viewPoint = superview?.convertPoint(point, toView: self) ?? point
let isInsideView = pointInside(viewPoint, withEvent: event)
var view = super.hitTest(viewPoint, withEvent: event)
return view
}
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
return CGRectContainsPoint(bounds, point)
}
}
如果您需要其他内容,但按钮在callout中响应,请在hitTest返回视图之前添加代码以处理特定视图中的触摸
if calloutState == .Expanded && CGRectContainsPoint(tableView.frame, viewPoint) {
view = tableView.hitTest(convertPoint(viewPoint, toView: tableView), withEvent: event)
}
func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!) {
if let mapPin = view as? MapPin {
updatePinPosition(mapPin)
}
}
func mapView(mapView: MKMapView!, didDeselectAnnotationView view: MKAnnotationView!) {
if let mapPin = view as? MapPin {
if mapPin.preventDeselection {
mapView.selectAnnotation(view.annotation, animated: false)
}
}
}
func updatePinPosition(pin:MapPin) {
let defaultShift:CGFloat = 50
let pinPosition = CGPointMake(pin.frame.midX, pin.frame.maxY)
let y = pinPosition.y - defaultShift
let controlPoint = CGPointMake(pinPosition.x, y)
let controlPointCoordinate = mapView.convertPoint(controlPoint, toCoordinateFromView: mapView)
mapView.setCenterCoordinate(controlPointCoordinate, animated: true)
}