element-ios/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserAnnotationCalloutView.s...

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)
}
}
}