Display user heading in location picker and location view

This commit is contained in:
Ilya Laktyushin 2020-07-28 16:58:19 +03:00
parent 3fbb7bf303
commit 3c0afd30d4
4 changed files with 103 additions and 5 deletions

View File

@ -71,6 +71,8 @@
bool _throttle;
TGLocationPinAnnotationView *_ownLiveLocationView;
__weak MKAnnotationView *_userLocationView;
UIImageView *_headingArrowView;
}
@end
@ -162,6 +164,8 @@
[_liveLocationsDisposable dispose];
[_reloadDisposable dispose];
[_frequentUpdatesDisposable dispose];
[_locationManager stopUpdatingHeading];
}
- (void)tg_setRightBarButtonItem:(UIBarButtonItem *)barButtonItem action:(bool)action animated:(bool)animated {
@ -438,6 +442,36 @@
{
[super loadView];
static UIImage *headingArrowImage = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(28.0f, 28.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, CGRectMake(0, 0, 28, 28));
CGContextSetFillColorWithColor(context, UIColorRGB(0x3393fe).CGColor);
CGContextMoveToPoint(context, 14, 0);
CGContextAddLineToPoint(context, 19, 7);
CGContextAddLineToPoint(context, 9, 7);
CGContextClosePath(context);
CGContextFillPath(context);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextFillEllipseInRect(context, CGRectMake(5.0, 5.0, 18.0, 18.0));
headingArrowImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
});
_headingArrowView = [[UIImageView alloc] init];
_headingArrowView.hidden = true;
_headingArrowView.frame = CGRectMake(0.0, 0.0, 28.0, 28.0);
_headingArrowView.image = headingArrowImage;
_tableView.scrollsToTop = false;
_mapView.tapEnabled = false;
@ -495,6 +529,8 @@
{
[super viewWillAppear:animated];
[_locationManager startUpdatingHeading];
if (self.previewMode && !animated)
{
UIView *contentView = [[_mapView subviews] firstObject];
@ -950,6 +986,9 @@
{
_userLocationView = view;
[_userLocationView addSubview:_headingArrowView];
_headingArrowView.center = CGPointMake(view.frame.size.width / 2.0, view.frame.size.height / 2.0);
if (_ownLiveLocationView != nil)
{
[_userLocationView addSubview:_ownLiveLocationView];
@ -982,6 +1021,14 @@
return CLLocationCoordinate2DMake(_locationAttachment.latitude, _locationAttachment.longitude);
}
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
if (newHeading != nil) {
_headingArrowView.hidden = false;
_headingArrowView.transform = CGAffineTransformMakeRotation(newHeading.magneticHeading / 180.0 * M_PI);
}
}
#pragma mark -
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

View File

@ -68,11 +68,30 @@ private class LocationMapView: MKMapView, UIGestureRecognizerDelegate {
}
}
private func generateHeadingArrowImage() -> UIImage? {
return generateImage(CGSize(width: 28.0, height: 28.0)) { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.setFillColor(UIColor(rgb: 0x3393fe).cgColor)
context.move(to: CGPoint(x: 14.0, y: 0.0))
context.addLine(to: CGPoint(x: 19.0, y: 7.0))
context.addLine(to: CGPoint(x: 9.0, y: 7.0))
context.closePath()
context.fillPath()
context.setBlendMode(.clear)
context.fillEllipse(in: bounds.insetBy(dx: 5.0, dy: 5.0))
}
}
final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
private let locationPromise = Promise<CLLocation?>(nil)
private let pickerAnnotationContainerView: PickerAnnotationContainerView
private weak var userLocationAnnotationView: MKAnnotationView?
private var headingArrowView: UIImageView?
private let pinDisposable = MetaDisposable()
@ -103,6 +122,10 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
override func didLoad() {
super.didLoad()
self.headingArrowView = UIImageView()
self.headingArrowView?.frame = CGRect(origin: CGPoint(), size: CGSize(width: 28.0, height: 28.0))
self.headingArrowView?.image = generateHeadingArrowImage()
self.mapView?.interactiveTransitionGestureRecognizerTest = { p in
if p.x > 44.0 {
return true
@ -232,6 +255,10 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
for view in views {
if view.annotation is MKUserLocation {
self.userLocationAnnotationView = view
if let headingArrowView = self.headingArrowView {
view.addSubview(headingArrowView)
headingArrowView.center = CGPoint(x: view.frame.width / 2.0, y: view.frame.height / 2.0)
}
if let annotationView = self.customUserLocationAnnotationView {
view.addSubview(annotationView)
}
@ -347,6 +374,18 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
var userHeading: CGFloat? = nil {
didSet {
if let heading = self.userHeading {
self.headingArrowView?.isHidden = false
self.headingArrowView?.transform = CGAffineTransform(rotationAngle: CGFloat(heading / 180.0 * CGFloat.pi))
} else {
self.headingArrowView?.isHidden = true
self.headingArrowView?.transform = CGAffineTransform.identity
}
}
}
var annotations: [LocationPinAnnotation] = [] {
didSet {
guard let mapView = self.mapView else {

View File

@ -289,7 +289,7 @@ public final class LocationPickerController: ViewController {
return
}
self.displayNode = LocationPickerControllerNode(context: self.context, presentationData: self.presentationData, mode: self.mode, interaction: interaction)
self.displayNode = LocationPickerControllerNode(context: self.context, presentationData: self.presentationData, mode: self.mode, interaction: interaction, locationManager: self.locationManager)
self.displayNodeDidLoad()
self.permissionDisposable = (DeviceAccess.authorizationStatus(subject: .location(.send))

View File

@ -17,6 +17,7 @@ import AppBundle
import CoreLocation
import Geocoding
import PhoneNumberFormat
import DeviceAccess
private struct LocationPickerTransaction {
let deletions: [ListViewDeleteItem]
@ -240,12 +241,13 @@ struct LocationPickerState {
}
}
final class LocationPickerControllerNode: ViewControllerTracingNode {
final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationManagerDelegate {
private let context: AccountContext
private var presentationData: PresentationData
private let presentationDataPromise: Promise<PresentationData>
private let mode: LocationPickerMode
private let interaction: LocationPickerInteraction
private let locationManager: LocationManager
private let listNode: ListView
private let emptyResultsTextNode: ImmediateTextNode
@ -269,12 +271,13 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
private var listOffset: CGFloat?
init(context: AccountContext, presentationData: PresentationData, mode: LocationPickerMode, interaction: LocationPickerInteraction) {
init(context: AccountContext, presentationData: PresentationData, mode: LocationPickerMode, interaction: LocationPickerInteraction, locationManager: LocationManager) {
self.context = context
self.presentationData = presentationData
self.presentationDataPromise = Promise(presentationData)
self.mode = mode
self.interaction = interaction
self.locationManager = locationManager
self.state = LocationPickerState()
self.statePromise = Promise(self.state)
@ -539,7 +542,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
switch previousState.selectedLocation {
case .none, .venue:
updateMap = true
case let .location(previousCoordinate, address):
case let .location(previousCoordinate, _):
if previousCoordinate != coordinate {
updateMap = true
}
@ -691,11 +694,20 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
strongSelf.goToUserLocation()
}
}
self.locationManager.manager.startUpdatingHeading()
self.locationManager.manager.delegate = self
}
deinit {
self.disposable?.dispose()
self.geocodingDisposable.dispose()
self.locationManager.manager.stopUpdatingHeading()
}
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
self.headerNode.mapNode.userHeading = CGFloat(newHeading.magneticHeading)
}
func updatePresentationData(_ presentationData: PresentationData) {
@ -727,7 +739,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
}
private func dequeueTransition() {
guard let layout = self.validLayout, let transition = self.enqueuedTransitions.first else {
guard let _ = self.validLayout, let transition = self.enqueuedTransitions.first else {
return
}
self.enqueuedTransitions.remove(at: 0)