自定义 iOS8 标注气泡 (Swift)
Posted
技术标签:
【中文标题】自定义 iOS8 标注气泡 (Swift)【英文标题】:Customise iOS8 Callout bubble (Swift) 【发布时间】:2015-03-19 22:29:45 【问题描述】:我想自定义单击 MKAnnotationView 时可视化的 ios8 MapView 标注气泡。默认气泡有点限制(只有标题、副标题和 2 个附件视图),所以我正在努力寻找替代解决方案。这里有两种可能的方法和我面临的相关问题:
问题 1) 创建自定义标注气泡
挖掘Apple documentation我发现了这个:
当您使用自定义视图而不是标准标注时,您需要 做额外的工作以确保您的标注正确显示和隐藏 当用户与之交互时。以下步骤概述了 创建一个包含按钮的自定义标注:
设计一个代表自定义的 NSView 或 UIView 子类 大喊。子类很可能需要实现 drawRect:绘制自定义内容的方法。创建视图控制器 初始化标注视图并执行与 按钮。在注解视图中,实现 hitTest: 来响应 在注释视图边界之外但在注释视图范围内的命中 标注视图的边界,如清单 6-7 所示。在注释中 视图,实现 setSelected:animated: 将标注视图添加为 用户单击或点击注释视图时的子视图。如果 当用户选择它时,标注视图已经可见, setSelected: 方法应该从 注释视图(见清单 6-8)。在注释视图的 initWithAnnotation: 方法,将 canShowCallout 属性设置为 NO 即可 防止地图在用户显示标准标注时 选择注释。清单 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: 到的示例 动画自定义标注视图的到达和关闭,当 用户选择注释视图。
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 个字符,我的辅助视图定位就会混乱。 如何避免这个问题?
【问题讨论】:
【参考方案1】: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)
如果您需要其他东西,但按钮在标注中响应,请添加代码以在 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)
【讨论】:
嗨,您的代码出现此错误。在“MapPin”类中。错误消息是“使用未声明的类型'MapPinModelProtocol'” 嗨。谢谢。我已经修正了那个错字。在我的情况下,我使用符合我的 MapPinModelProtocol 的自定义 MKAnnotation 类。它只是声明了一些额外的字段。不管怎样,这里是@objc protocol MapPinModelProtocol: MKAnnotation var points:String get var photoUrls:[String] get
maksimko 当我输入代码时,我得到“updatePinPosition not found”。我正在尝试在标注气泡中找到完全自定义的 uiview 弹出窗口所需的代码。你能把你的完整代码放上来吗?
嗨,@HixField。谢谢。添加了缺少的 updatePinPosition 方法。我会尽快添加完成代码的链接。
嗨。非常好的解决方案你有没有想过创建一个库并将其发布到 GitHub 上?以上是关于自定义 iOS8 标注气泡 (Swift)的主要内容,如果未能解决你的问题,请参考以下文章
更改自定义 UIButton 的 TitleColor (iOS 8 + swift)