# Switch between user tracking modes

> **Note (legacy): A newer version of the Maps SDK is available**
> 
> This page uses v6.4.1 of the Mapbox Maps SDK. A newer version of the SDK is available. Learn about the latest version, v11.29.0, in the [Maps SDK documentation](https://docs.mapbox.com/ios/ja/maps/examples/tracking-mode/).

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/ios/ja/ios/ja/assets/medias/UserTrackingModesExample-a255ccbb7fad46571d11cbce94792f89.mp4).

This example uses two classes within a single file:

-   `UserLocationButton` is a subclass of `UIButton` that updates its style when the tracking mode state changes.
-   `ViewController` creates the [`MGLMapView`](https://docs.mapbox.com/ios/maps/api/6.4.1/Classes/MGLMapView.html) and allows the user to toggle between [`MGLUserTrackingMode`s](https://docs.mapbox.com/ios/maps/api/6.4.1/Enums/MGLUserTrackingMode.html) using the `UserLocationButton`.

**Swift**

Title: `ViewController`

[View on GitHub](https://github.com/mapbox/ios-sdk-examples/tree/298e050be7352eb28cee6f03e02945593140c1f3/Examples/Swift/UserTrackingModesExample.swift)

```swift
import Mapbox

class ViewController: UIViewController, MGLMapViewDelegate {
    var mapView: MGLMapView!
    var userLocationButton: UserLocationButton?

    override func viewDidLoad() {
        super.viewDidLoad()

        mapView = MGLMapView(frame: view.bounds, styleURL: MGLStyle.darkStyleURL)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.delegate = self

        // The user location annotation takes its color from the map view's tint color.
        mapView.tintColor = .red
        mapView.attributionButton.tintColor = .lightGray

        // Enable the always-on heading indicator for the user location annotation.
        mapView.showsUserHeadingIndicator = true

        view.addSubview(mapView)

        // Create button to allow user to change the tracking mode.
        setupLocationButton()
    }

    // Update the button state when the user tracking mode updates or resets.
    func mapView(_ mapView: MGLMapView, didChange mode: MGLUserTrackingMode, animated: Bool) {
        guard let userLocationButton = userLocationButton else { return }
        userLocationButton.updateArrowForTrackingMode(mode: mode)
    }

    // Update the user tracking mode when the user toggles through the
    // user tracking mode button.
    @IBAction func locationButtonTapped(sender: UserLocationButton) {
        var mode: MGLUserTrackingMode

        switch (mapView.userTrackingMode) {
        case .none:
            mode = .follow
        case .follow:
            mode = .followWithHeading
        case .followWithHeading:
            mode = .followWithCourse
        case .followWithCourse:
            mode = .none
        @unknown default:
            fatalError("Unknown user tracking mode")
        }

        mapView.userTrackingMode = mode
    }

    // Button creation and autolayout setup
    func setupLocationButton() {
        let userLocationButton = UserLocationButton(buttonSize: 80)
        userLocationButton.addTarget(self, action: #selector(locationButtonTapped), for: .touchUpInside)
        userLocationButton.tintColor = mapView.tintColor

        // Setup constraints such that the button is placed within
        // the upper left corner of the view.
        userLocationButton.translatesAutoresizingMaskIntoConstraints = false

        var leadingConstraintSecondItem: AnyObject
        if #available(iOS 11.0, *) {
            leadingConstraintSecondItem = view.safeAreaLayoutGuide
        } else {
            leadingConstraintSecondItem = view
        }

        let constraints: [NSLayoutConstraint] = [
            NSLayoutConstraint(item: userLocationButton, attribute: .top, relatedBy: .greaterThanOrEqual, toItem: topLayoutGuide, attribute: .bottom, multiplier: 1, constant: 10),
            NSLayoutConstraint(item: userLocationButton, attribute: .leading, relatedBy: .equal, toItem: leadingConstraintSecondItem, attribute: .leading, multiplier: 1, constant: 10),
            NSLayoutConstraint(item: userLocationButton, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: userLocationButton.frame.size.height),
            NSLayoutConstraint(item: userLocationButton, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: userLocationButton.frame.size.width)
        ]

        view.addSubview(userLocationButton)
        view.addConstraints(constraints)

        self.userLocationButton = userLocationButton
    }
}

// MARK: - Custom UIButton subclass

class UserLocationButton: UIButton {
    private var arrow: CAShapeLayer?
    private let buttonSize: CGFloat

    // Initializer to create the user tracking mode button
    init(buttonSize: CGFloat) {
        self.buttonSize = buttonSize
        super.init(frame: CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize))
        self.backgroundColor = UIColor.white.withAlphaComponent(0.9)
        self.layer.cornerRadius = 4

        let arrow = CAShapeLayer()
        arrow.path = arrowPath()
        arrow.lineWidth = 2
        arrow.lineJoin = CAShapeLayerLineJoin.round
        arrow.bounds = CGRect(x: 0, y: 0, width: buttonSize / 2, height: buttonSize / 2)
        arrow.position = CGPoint(x: buttonSize / 2, y: buttonSize / 2)
        arrow.shouldRasterize = true
        arrow.rasterizationScale = UIScreen.main.scale
        arrow.drawsAsynchronously = true

        self.arrow = arrow

        // Update arrow for initial tracking mode
        updateArrowForTrackingMode(mode: .none)
        layer.addSublayer(self.arrow!)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // Create a new bezier path to represent the tracking mode arrow,
    // making sure the arrow does not get drawn outside of the
    // frame size of the UIButton.
    private func arrowPath() -> CGPath {
        let bezierPath = UIBezierPath()
        let max: CGFloat = buttonSize / 2
        bezierPath.move(to: CGPoint(x: max * 0.5, y: 0))
        bezierPath.addLine(to: CGPoint(x: max * 0.1, y: max))
        bezierPath.addLine(to: CGPoint(x: max * 0.5, y: max * 0.65))
        bezierPath.addLine(to: CGPoint(x: max * 0.9, y: max))
        bezierPath.addLine(to: CGPoint(x: max * 0.5, y: 0))
        bezierPath.close()

        return bezierPath.cgPath
    }

    // Update the arrow's color and rotation when tracking mode is changed.
    func updateArrowForTrackingMode(mode: MGLUserTrackingMode) {
        let activePrimaryColor = UIColor.red
        let disabledPrimaryColor = UIColor.clear
        let disabledSecondaryColor = UIColor.black
        let rotatedArrow = CGFloat(0.66)

        switch mode {
        case .none:
            updateArrow(fillColor: disabledPrimaryColor, strokeColor: disabledSecondaryColor, rotation: 0)
        case .follow:
            updateArrow(fillColor: disabledPrimaryColor, strokeColor: activePrimaryColor, rotation: 0)
        case .followWithHeading:
            updateArrow(fillColor: activePrimaryColor, strokeColor: activePrimaryColor, rotation: rotatedArrow)
        case .followWithCourse:
            updateArrow(fillColor: activePrimaryColor, strokeColor: activePrimaryColor, rotation: 0)
        @unknown default:
            fatalError("Unknown user tracking mode")
        }
    }

    func updateArrow(fillColor: UIColor, strokeColor: UIColor, rotation: CGFloat) {
        guard let arrow = arrow else { return }
        arrow.fillColor = fillColor.cgColor
        arrow.strokeColor = strokeColor.cgColor
        arrow.setAffineTransform(CGAffineTransform.identity.rotated(by: rotation))

        // Re-center the arrow within the button if rotated
        if rotation > 0 {
            arrow.position = CGPoint(x: buttonSize / 2 + 2, y: buttonSize / 2 - 2)
        }

        layoutIfNeeded()
    }
}
```

**Objective C**

Title: `ViewController`

[View on GitHub](https://github.com/mapbox/ios-sdk-examples/tree/298e050be7352eb28cee6f03e02945593140c1f3/Examples/ObjectiveC/UserTrackingModesExample.m)

```swift
#import "ViewController.h"
@import Mapbox;
#pragma mark - UIButton subclass

// Subclass UIButton to create a custom user tracking mode button
@interface UserLocationButton : UIButton

@property (nonatomic) CAShapeLayer *arrow;
@property (nonatomic) CGFloat buttonSize;

@end

@implementation UserLocationButton

// Initializer to create the user tracking mode button
- (instancetype)initWithButtonSize:(CGFloat)buttonSize {
    if (self = [super init]) {
        self.frame = CGRectMake(0, 0, buttonSize, buttonSize);
        self.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9f];
        self.layer.cornerRadius = 4;
        self.buttonSize = buttonSize;
        
        CAShapeLayer *arrow = [[CAShapeLayer alloc] init];
        arrow.path = [self arrowPath];
        arrow.lineWidth = 2;
        arrow.lineJoin = kCALineJoinRound;
        arrow.bounds = CGRectMake(0, 0, buttonSize / 2, buttonSize / 2);
        arrow.position = CGPointMake(buttonSize / 2, buttonSize / 2);
        arrow.shouldRasterize = YES;
        arrow.rasterizationScale = [[UIScreen mainScreen] scale];
        arrow.drawsAsynchronously = YES;
        
        self.arrow = arrow;
        
        // Update arrow for initial tracking mode
        [self updateArrowForTrackingMode:MGLUserTrackingModeNone];
        
        [self.layer addSublayer:self.arrow];
    }
    
    return self;
}

// Create a new bezier path to represent the tracking mode arrow,
// making sure the arrow does not get drawn outside of the
// frame size of the UIButton.
- (CGPathRef)arrowPath {
    UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
    CGFloat max = self.buttonSize / 2;
    [bezierPath moveToPoint:CGPointMake(max * 0.5f,    0)];
    [bezierPath addLineToPoint:CGPointMake(max * 0.1f, max)];
    [bezierPath addLineToPoint:CGPointMake(max * 0.5f, max * 0.65f)];
    [bezierPath addLineToPoint:CGPointMake(max * 0.9f, max)];
    [bezierPath addLineToPoint:CGPointMake(max * 0.5f, 0)];
    [bezierPath closePath];
    
    return bezierPath.CGPath;
}

// Update the arrow's color and rotation when tracking mode is changed
- (void)updateArrowForTrackingMode:(MGLUserTrackingMode)mode {
    UIColor *activePrimaryColor = UIColor.redColor;
    UIColor *disabledPrimaryColor = UIColor.clearColor;
    UIColor *disabledSecondaryColor = UIColor.blackColor;
    CGFloat rotatedArrow = 0.66f;
    
    switch (mode) {
        case MGLUserTrackingModeNone:
            [self updateArrowFillColor:disabledPrimaryColor
                           strokeColor:disabledSecondaryColor
                              rotation:0];
            break;
        case MGLUserTrackingModeFollow:
            [self updateArrowFillColor:disabledPrimaryColor
                           strokeColor:activePrimaryColor
                              rotation:0];
            break;
        case MGLUserTrackingModeFollowWithHeading:
            [self updateArrowFillColor:activePrimaryColor
                           strokeColor:activePrimaryColor
                              rotation:rotatedArrow];
            break;
        case MGLUserTrackingModeFollowWithCourse:
            [self updateArrowFillColor:activePrimaryColor
                           strokeColor:activePrimaryColor
                              rotation:0];
            break;
    }
}

- (void)updateArrowFillColor:(UIColor*)fillColor strokeColor:(UIColor*) strokeColor rotation:(CGFloat) rotation {
    self.arrow.fillColor = fillColor.CGColor;
    self.arrow.strokeColor = strokeColor.CGColor;
    self.arrow.affineTransform = CGAffineTransformMakeRotation(rotation);
    
    // Re-center the arrow within the button if rotated
    if (rotation > 0) {
        self.arrow.position = CGPointMake(self.buttonSize / 2 + 2, self.buttonSize / 2 - 2);
    }
    
    [self layoutIfNeeded];
}

@end

#pragma mark - ViewController

@interface ViewController () <MGLMapViewDelegate>

@property (nonatomic) MGLMapView *mapView;
@property (nonatomic) UserLocationButton *userLocationButton;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:[MGLStyle darkStyleURL]];
    self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.mapView.delegate = self;

    // The user location annotation takes its color from the map view's tint color.
    self.mapView.tintColor = [UIColor redColor];
    self.mapView.attributionButton.tintColor = [UIColor lightGrayColor];

    // Enable the always-on heading indicator for the user location annotation.
    self.mapView.showsUserHeadingIndicator = YES;

    [self.view addSubview:self.mapView];
    
    // Create button to allow user to change the tracking mode.
    [self setupLocationButton];
}

// Update the button state when the user tracking mode updates or resets.
- (void)mapView:(MGLMapView *)mapView didChangeUserTrackingMode:(MGLUserTrackingMode)mode animated:(BOOL)animated {
    [self.userLocationButton updateArrowForTrackingMode:mode];
}

// Update the user tracking mode when the user toggles through the
// user tracking mode button.
- (void)locationButtonTapped:(UserLocationButton *)sender {
    MGLUserTrackingMode mode;
    
    switch (self.mapView.userTrackingMode) {
        case MGLUserTrackingModeNone:
            mode = MGLUserTrackingModeFollow;
            break;
        case MGLUserTrackingModeFollow:
            mode = MGLUserTrackingModeFollowWithHeading;
            break;
        case MGLUserTrackingModeFollowWithHeading:
            mode = MGLUserTrackingModeFollowWithCourse;
            break;
        case MGLUserTrackingModeFollowWithCourse:
            mode = MGLUserTrackingModeNone;
            break;
    }

    [self.mapView setUserTrackingMode:mode];
}

// Button creation and autolayout setup
- (void)setupLocationButton {
    UserLocationButton *userLocationButton = [[UserLocationButton alloc] initWithButtonSize:80];
    [userLocationButton addTarget:self action:@selector(locationButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
    userLocationButton.tintColor = self.mapView.tintColor;

    // Setup constraints such that the button is placed within
    // the upper left corner of the view.
    userLocationButton.translatesAutoresizingMaskIntoConstraints = NO;

    id leadingConstraintSecondItem;
    if (@available(iOS 11.0, *)) {
        leadingConstraintSecondItem = self.view.safeAreaLayoutGuide;
    } else {
        leadingConstraintSecondItem = self.view;
    }

    NSArray<NSLayoutConstraint *> *constraints = @[
      [NSLayoutConstraint constraintWithItem:userLocationButton attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1 constant:10],
      [NSLayoutConstraint constraintWithItem:userLocationButton attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:leadingConstraintSecondItem attribute:NSLayoutAttributeLeading multiplier:1 constant:10],
      [NSLayoutConstraint constraintWithItem:userLocationButton attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:userLocationButton.frame.size.height],
      [NSLayoutConstraint constraintWithItem:userLocationButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:userLocationButton.frame.size.width]
    ];

    [self.view addSubview:userLocationButton];
    [self.view addConstraints:constraints];

    self.userLocationButton = userLocationButton;
}

@end


```