144 lines
4.1 KiB
Swift
144 lines
4.1 KiB
Swift
//
|
|
// Copyright 2022-2024 New Vector Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
// Please see LICENSE in the repository root for full details.
|
|
//
|
|
|
|
import Mapbox
|
|
|
|
class UserAnnotationCalloutView: UIView, MGLCalloutView, Themable {
|
|
// MARK: - Constants
|
|
|
|
private enum Constants {
|
|
static let animationDuration: TimeInterval = 0.2
|
|
static let bottomMargin: CGFloat = 3.0
|
|
}
|
|
|
|
// MARK: - Properties
|
|
|
|
// MARK: Overrides
|
|
|
|
var representedObject: MGLAnnotation
|
|
|
|
lazy var leftAccessoryView = UIView()
|
|
|
|
lazy var rightAccessoryView = UIView()
|
|
|
|
var delegate: MGLCalloutViewDelegate?
|
|
|
|
// Allow the callout to remain open during panning.
|
|
let dismissesAutomatically = false
|
|
|
|
let isAnchoredToAnnotation = true
|
|
|
|
// https://github.com/mapbox/mapbox-gl-native/issues/9228
|
|
override var center: CGPoint {
|
|
set {
|
|
var newCenter = newValue
|
|
newCenter.y -= bounds.midY + Constants.bottomMargin
|
|
super.center = newCenter
|
|
}
|
|
get {
|
|
super.center
|
|
}
|
|
}
|
|
|
|
// MARK: Private
|
|
|
|
lazy var contentView = UserAnnotationCalloutContentView.instantiate()
|
|
|
|
// MARK: - Setup
|
|
|
|
required init(userLocationAnnotation: UserLocationAnnotation) {
|
|
representedObject = userLocationAnnotation
|
|
|
|
super.init(frame: .zero)
|
|
|
|
vc_addSubViewMatchingParent(contentView)
|
|
|
|
update(theme: ThemeService.shared().theme)
|
|
|
|
let size = UserAnnotationCalloutContentView.contentViewSize()
|
|
|
|
frame = CGRect(origin: .zero, size: size)
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder decoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
// MARK: - Public
|
|
|
|
func update(theme: Theme) {
|
|
contentView.update(theme: theme)
|
|
}
|
|
|
|
// MARK: - Overrides
|
|
|
|
func presentCallout(from rect: CGRect, in view: UIView, constrainedTo constrainedRect: CGRect, animated: Bool) {
|
|
// Set callout above the marker view
|
|
|
|
center = view.center.applying(CGAffineTransform(translationX: 0, y: view.bounds.height / 2 + bounds.height))
|
|
|
|
delegate?.calloutViewWillAppear?(self)
|
|
|
|
view.addSubview(self)
|
|
|
|
if isCalloutTappable() {
|
|
// Handle taps and eventually try to send them to the delegate (usually the map view).
|
|
contentView.shareButton.addTarget(self, action: #selector(UserAnnotationCalloutView.calloutTapped), for: .touchUpInside)
|
|
} else {
|
|
// Disable tapping and highlighting.
|
|
contentView.shareButton.isUserInteractionEnabled = false
|
|
}
|
|
|
|
if animated {
|
|
alpha = 0
|
|
|
|
UIView.animate(withDuration: Constants.animationDuration) { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
strongSelf.alpha = 1
|
|
strongSelf.delegate?.calloutViewDidAppear?(strongSelf)
|
|
}
|
|
} else {
|
|
delegate?.calloutViewDidAppear?(self)
|
|
}
|
|
}
|
|
|
|
func dismissCallout(animated: Bool) {
|
|
if superview != nil {
|
|
if animated {
|
|
UIView.animate(withDuration: Constants.animationDuration, animations: { [weak self] in
|
|
self?.alpha = 0
|
|
}, completion: { [weak self] _ in
|
|
self?.removeFromSuperview()
|
|
})
|
|
} else {
|
|
removeFromSuperview()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Callout interaction handlers
|
|
|
|
func isCalloutTappable() -> Bool {
|
|
if let delegate = delegate {
|
|
if delegate.responds(to: #selector(MGLCalloutViewDelegate.calloutViewShouldHighlight)) {
|
|
return delegate.calloutViewShouldHighlight!(self)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
@objc func calloutTapped() {
|
|
if isCalloutTappable(), delegate!.responds(to: #selector(MGLCalloutViewDelegate.calloutViewTapped)) {
|
|
delegate!.calloutViewTapped!(self)
|
|
}
|
|
}
|
|
}
|