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; bool _throttle;
TGLocationPinAnnotationView *_ownLiveLocationView; TGLocationPinAnnotationView *_ownLiveLocationView;
__weak MKAnnotationView *_userLocationView; __weak MKAnnotationView *_userLocationView;
UIImageView *_headingArrowView;
} }
@end @end
@@ -162,6 +164,8 @@
[_liveLocationsDisposable dispose]; [_liveLocationsDisposable dispose];
[_reloadDisposable dispose]; [_reloadDisposable dispose];
[_frequentUpdatesDisposable dispose]; [_frequentUpdatesDisposable dispose];
[_locationManager stopUpdatingHeading];
} }
- (void)tg_setRightBarButtonItem:(UIBarButtonItem *)barButtonItem action:(bool)action animated:(bool)animated { - (void)tg_setRightBarButtonItem:(UIBarButtonItem *)barButtonItem action:(bool)action animated:(bool)animated {
@@ -438,6 +442,36 @@
{ {
[super loadView]; [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; _tableView.scrollsToTop = false;
_mapView.tapEnabled = false; _mapView.tapEnabled = false;
@@ -495,6 +529,8 @@
{ {
[super viewWillAppear:animated]; [super viewWillAppear:animated];
[_locationManager startUpdatingHeading];
if (self.previewMode && !animated) if (self.previewMode && !animated)
{ {
UIView *contentView = [[_mapView subviews] firstObject]; UIView *contentView = [[_mapView subviews] firstObject];
@@ -950,6 +986,9 @@
{ {
_userLocationView = view; _userLocationView = view;
[_userLocationView addSubview:_headingArrowView];
_headingArrowView.center = CGPointMake(view.frame.size.width / 2.0, view.frame.size.height / 2.0);
if (_ownLiveLocationView != nil) if (_ownLiveLocationView != nil)
{ {
[_userLocationView addSubview:_ownLiveLocationView]; [_userLocationView addSubview:_ownLiveLocationView];
@@ -982,6 +1021,14 @@
return CLLocationCoordinate2DMake(_locationAttachment.latitude, _locationAttachment.longitude); 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 - #pragma mark -
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section - (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 { final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
private let locationPromise = Promise<CLLocation?>(nil) private let locationPromise = Promise<CLLocation?>(nil)
private let pickerAnnotationContainerView: PickerAnnotationContainerView private let pickerAnnotationContainerView: PickerAnnotationContainerView
private weak var userLocationAnnotationView: MKAnnotationView? private weak var userLocationAnnotationView: MKAnnotationView?
private var headingArrowView: UIImageView?
private let pinDisposable = MetaDisposable() private let pinDisposable = MetaDisposable()
@@ -103,6 +122,10 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
override func didLoad() { override func didLoad() {
super.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 self.mapView?.interactiveTransitionGestureRecognizerTest = { p in
if p.x > 44.0 { if p.x > 44.0 {
return true return true
@@ -232,6 +255,10 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
for view in views { for view in views {
if view.annotation is MKUserLocation { if view.annotation is MKUserLocation {
self.userLocationAnnotationView = view 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 { if let annotationView = self.customUserLocationAnnotationView {
view.addSubview(annotationView) 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] = [] { var annotations: [LocationPinAnnotation] = [] {
didSet { didSet {
guard let mapView = self.mapView else { guard let mapView = self.mapView else {

View File

@@ -289,7 +289,7 @@ public final class LocationPickerController: ViewController {
return 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.displayNodeDidLoad()
self.permissionDisposable = (DeviceAccess.authorizationStatus(subject: .location(.send)) self.permissionDisposable = (DeviceAccess.authorizationStatus(subject: .location(.send))

View File

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