diff --git a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h
index a81f261b43..0272047ba5 100644
--- a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h
+++ b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h
@@ -5,6 +5,7 @@
 
 @class MWMMapSearchResult;
 @class TrackInfo;
+@class ElevationProfileData;
 
 typedef NS_ENUM(NSUInteger, MWMZoomMode) { MWMZoomModeIn = 0, MWMZoomModeOut };
 
@@ -20,14 +21,17 @@ NS_ASSUME_NONNULL_BEGIN
 typedef void (^SearchInDownloaderCompletions)(NSArray<MWMMapSearchResult *> *results, BOOL finished);
 typedef void (^TrackRecordingUpdatedHandler)(TrackInfo * _Nonnull trackInfo);
 
-@protocol TrackRecorder <NSObject>
+@protocol TrackRecorder
 
 + (void)startTrackRecording;
 + (void)setTrackRecordingUpdateHandler:(TrackRecordingUpdatedHandler _Nullable)trackRecordingDidUpdate;
 + (void)stopTrackRecording;
-+ (void)saveTrackRecordingWithName:(nullable NSString *)name;
++ (void)saveTrackRecordingWithName:(nonnull NSString *)name;
 + (BOOL)isTrackRecordingEnabled;
 + (BOOL)isTrackRecordingEmpty;
+/// Returns current track recording elevation info.
+/// If the track recording is not in progress, returns empty ElevationProfileData.
++ (ElevationProfileData * _Nonnull)trackRecordingElevationInfo;
 
 @end
 
diff --git a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm
index d00c60df64..47f6fe1461 100644
--- a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm
+++ b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm
@@ -3,6 +3,7 @@
 #import "ProductsConfiguration+Core.h"
 #import "Product+Core.h"
 #import "TrackInfo+Core.h"
+#import "ElevationProfileData+Core.h"
 
 #include "Framework.h"
 
@@ -234,8 +235,8 @@ static Framework::ProductsPopupCloseReason ConvertProductPopupCloseReasonToCore(
   GetFramework().StopTrackRecording();
 }
 
-+ (void)saveTrackRecordingWithName:(nullable NSString *)name {
-  GetFramework().SaveTrackRecordingWithName(name == nil ? "" : name.UTF8String);
++ (void)saveTrackRecordingWithName:(nonnull NSString *)name {
+  GetFramework().SaveTrackRecordingWithName(name.UTF8String);
 }
 
 + (BOOL)isTrackRecordingEnabled {
@@ -246,6 +247,10 @@ static Framework::ProductsPopupCloseReason ConvertProductPopupCloseReasonToCore(
   return GetFramework().IsTrackRecordingEmpty();
 }
 
++ (ElevationProfileData * _Nonnull)trackRecordingElevationInfo {
+  return [[ElevationProfileData alloc] initWithElevationInfo:GetFramework().GetTrackRecordingElevationInfo()];
+}
+
 // MARK: - ProductsManager
 
 + (nullable ProductsConfiguration *)getProductsConfiguration {
diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.h b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.h
index ab25289cb4..8b5a10fa6d 100644
--- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.h
+++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.h
@@ -1,6 +1,7 @@
 #import <Foundation/Foundation.h>
 
 @class PlacePageScheduleData;
+@class TrackInfo;
 
 typedef NS_ENUM(NSInteger, PlacePageDataHotelType) {
   PlacePageDataHotelTypeHotel,
@@ -39,6 +40,8 @@ NS_ASSUME_NONNULL_BEGIN
 @property(nonatomic, readonly) PlacePageDataSchedule schedule;
 @property(nonatomic, readonly) BOOL isMyPosition;
 
+- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo;
+
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.mm b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.mm
index 9aed885cbb..785d906302 100644
--- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.mm
+++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.mm
@@ -1,4 +1,8 @@
 #import "PlacePagePreviewData+Core.h"
+#import "DistanceFormatter.h"
+#import "AltitudeFormatter.h"
+#import "DurationFormatter.h"
+#import "TrackInfo.h"
 
 #include "3party/opening_hours/opening_hours.hpp"
 
@@ -46,6 +50,18 @@ static PlacePageDataSchedule convertOpeningHours(std::string_view rawOH)
 
 @implementation PlacePagePreviewData
 
+- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo {
+  self = [super init];
+  if (self) {
+    // TODO: (KK) Replace separator with a shared static constant.
+    NSString * kSeparator = @" • ";
+    NSString * duration = [DurationFormatter durationStringFromTimeInterval:trackInfo.duration];
+    NSString * distance = [DistanceFormatter distanceStringFromMeters:trackInfo.distance];
+    _title = [@[duration, distance] componentsJoinedByString:kSeparator];
+  }
+  return self;
+}
+
 @end
 
 @implementation PlacePagePreviewData (Core)
diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.h b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.h
index 0a6ad57bf4..fa83eb897a 100644
--- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.h
+++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.h
@@ -10,8 +10,10 @@ NS_ASSUME_NONNULL_BEGIN
 
 @property(nonatomic, readonly) MWMTrackID trackId;
 @property(nonatomic, readonly) MWMMarkGroupID groupId;
-@property(nonatomic, readonly, nonnull) TrackInfo * trackInfo;
-@property(nonatomic, readonly, nullable) ElevationProfileData * elevationProfileData;
+@property(nonatomic, readwrite, nonnull) TrackInfo * trackInfo;
+@property(nonatomic, readwrite, nullable) ElevationProfileData * elevationProfileData;
+
+- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo;
 
 @end
 
diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.mm b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.mm
index ad421d8f16..d8b4ff89fe 100644
--- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.mm
+++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.mm
@@ -4,6 +4,15 @@
 
 @implementation PlacePageTrackData
 
+- (nonnull instancetype)initWithTrackInfo:(TrackInfo *)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo {
+  self = [super init];
+  if (self) {
+    _trackInfo = trackInfo;
+    _elevationProfileData = elevationInfo;
+  }
+  return self;
+}
+
 @end
 
 @implementation PlacePageTrackData (Core)
diff --git a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData+Core.h b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData+Core.h
index 48c63cde20..0a00cabca2 100644
--- a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData+Core.h
+++ b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData+Core.h
@@ -11,6 +11,7 @@ NS_ASSUME_NONNULL_BEGIN
                   elevationInfo:(ElevationInfo const &)elevationInfo
                     activePoint:(double)activePoint
                      myPosition:(double)myPosition;
+- (instancetype)initWithElevationInfo:(ElevationInfo const &)elevationInfo;
 
 @end
 
diff --git a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.h b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.h
index f8b5a070e3..2087a30a2f 100644
--- a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.h
+++ b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.h
@@ -13,6 +13,7 @@ typedef NS_ENUM(NSInteger, ElevationDifficulty) {
 @interface ElevationProfileData : NSObject
 
 @property(nonatomic, readonly) uint64_t trackId;
+@property(nonatomic, readonly) BOOL isTrackRecording;
 @property(nonatomic, readonly) ElevationDifficulty difficulty;
 @property(nonatomic, readonly) NSArray<ElevationHeightPoint *> * points;
 @property(nonatomic, readonly) double activePoint;
diff --git a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.mm b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.mm
index 69e89c9388..ffdd63c2d4 100644
--- a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.mm
+++ b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.mm
@@ -30,23 +30,36 @@ static ElevationDifficulty convertDifficulty(uint8_t difficulty) {
   if (self) {
     _trackId = trackId;
     _difficulty = convertDifficulty(elevationInfo.GetDifficulty());
-
-    auto const & points = elevationInfo.GetPoints();
-    NSMutableArray * pointsArray = [[NSMutableArray alloc] initWithCapacity:points.size()];
-    for (auto const & point : points) {
-      auto pointLatLon = mercator::ToLatLon(point.m_point.GetPoint());
-      CLLocationCoordinate2D coordinates = CLLocationCoordinate2DMake(pointLatLon.m_lat, pointLatLon.m_lon);
-      ElevationHeightPoint * elevationPoint = [[ElevationHeightPoint alloc] initWithCoordinates:coordinates
-                                                                                       distance:point.m_distance
-                                                                                    andAltitude:point.m_point.GetAltitude()];
-      [pointsArray addObject:elevationPoint];
-    }
-    _points = [pointsArray copy];
+    _points = [ElevationProfileData pointsFromElevationInfo:elevationInfo];
     _activePoint = activePoint;
     _myPosition = myPosition;
+    _isTrackRecording = false;
   }
   return self;
 }
 
+- (instancetype)initWithElevationInfo:(ElevationInfo const &)elevationInfo {
+  self = [super init];
+  if (self) {
+    _difficulty = convertDifficulty(elevationInfo.GetDifficulty());
+    _points = [ElevationProfileData pointsFromElevationInfo:elevationInfo];
+    _isTrackRecording = true;
+  }
+  return self;
+}
+
++ (NSArray<ElevationHeightPoint *> *)pointsFromElevationInfo:(ElevationInfo const &)elevationInfo {
+  auto const & points = elevationInfo.GetPoints();
+  NSMutableArray * pointsArray = [[NSMutableArray alloc] initWithCapacity:points.size()];
+  for (auto const & point : points) {
+    auto pointLatLon = mercator::ToLatLon(point.m_point.GetPoint());
+    CLLocationCoordinate2D coordinates = CLLocationCoordinate2DMake(pointLatLon.m_lat, pointLatLon.m_lon);
+    ElevationHeightPoint * elevationPoint = [[ElevationHeightPoint alloc] initWithCoordinates:coordinates
+                                                                                     distance:point.m_distance
+                                                                                  andAltitude:point.m_point.GetAltitude()];
+    [pointsArray addObject:elevationPoint];
+  }
+  return [pointsArray copy];
+}
 
 @end
diff --git a/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.h b/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.h
index c3c198816d..99736a9707 100644
--- a/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.h
+++ b/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.h
@@ -10,6 +10,7 @@
 @class PlacePageBookmarkData;
 @class MWMMapNodeAttributes;
 @class TrackInfo;
+@class ElevationProfileData;
 
 typedef NS_ENUM(NSInteger, PlacePageRoadType) {
   PlacePageRoadTypeToll,
@@ -49,12 +50,15 @@ NS_ASSUME_NONNULL_BEGIN
 @property(nonatomic, readonly) CLLocationCoordinate2D locationCoordinate;
 @property(nonatomic, copy, nullable) MWMVoidBlock onBookmarkStatusUpdate;
 @property(nonatomic, copy, nullable) MWMVoidBlock onMapNodeStatusUpdate;
+@property(nonatomic, copy, nullable) MWMVoidBlock onTrackRecordingProgressUpdate;
 @property(nonatomic, copy, nullable) void (^onMapNodeProgressUpdate)(uint64_t downloadedBytes, uint64_t totalBytes);
 
 - (instancetype)initWithLocalizationProvider:(id<IOpeningHoursLocalization>)localization;
+- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo;
 - (instancetype)init NS_UNAVAILABLE;
 
 - (void)updateBookmarkStatus;
+- (void)updateWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo;
 
 @end
 
diff --git a/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.mm b/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.mm
index cda7f27918..491452b0a2 100644
--- a/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.mm
+++ b/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.mm
@@ -5,6 +5,7 @@
 #import "PlacePageInfoData+Core.h"
 #import "PlacePageBookmarkData+Core.h"
 #import "PlacePageTrackData+Core.h"
+#import "ElevationProfileData+Core.h"
 #import "MWMMapNodeAttributes.h"
 
 #include <CoreApi/CoreApi.h>
@@ -84,6 +85,25 @@ static PlacePageRoadType convertRoadType(RoadWarningMarkType roadType) {
   return self;
 }
 
+- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo {
+  self = [super init];
+  if (self) {
+    _objectType = PlacePageObjectTypeTrackRecording;
+    _roadType = PlacePageRoadTypeNone;
+    _previewData = [[PlacePagePreviewData alloc] initWithTrackInfo:trackInfo];
+    _trackData = [[PlacePageTrackData alloc] initWithTrackInfo:trackInfo elevationInfo:elevationInfo];
+  }
+  return self;
+}
+
+- (void)updateWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo {
+  _previewData = [[PlacePagePreviewData alloc] initWithTrackInfo:trackInfo];
+  _trackData.trackInfo = trackInfo;
+  _trackData.elevationProfileData = elevationInfo;
+  if (self.onTrackRecordingProgressUpdate != nil)
+    self.onTrackRecordingProgressUpdate();
+}
+
 - (void)dealloc {
   if (self.mapNodeAttributes != nil) {
     [[MWMStorage sharedStorage] removeObserver:self];
diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h
index b57d9bef95..3d109865f3 100644
--- a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h
+++ b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h
@@ -4,7 +4,13 @@
 
 @class MapViewController;
 @class BottomTabBarViewController;
-@class TrackRecordingViewController;
+@class TrackRecordingButtonViewController;
+
+typedef NS_ENUM(NSUInteger, TrackRecordingButtonState) {
+  TrackRecordingButtonStateHidden,
+  TrackRecordingButtonStateVisible,
+  TrackRecordingButtonStateClosed,
+};
 
 @protocol MWMFeatureHolder;
 
@@ -20,7 +26,7 @@
 @property(nonatomic) MWMBottomMenuState menuRestoreState;
 @property(nonatomic) BOOL isDirectionViewHidden;
 @property(nonatomic) BottomTabBarViewController * tabBarController;
-@property(nonatomic) TrackRecordingViewController * trackRecordingButton;
+@property(nonatomic) TrackRecordingButtonViewController * trackRecordingButton;
 
 - (instancetype)init __attribute__((unavailable("init is not available")));
 - (instancetype)initWithParentController:(MapViewController *)controller;
@@ -34,6 +40,8 @@
 - (void)viewWillTransitionToSize:(CGSize)size
        withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator;
 
+- (void)setTrackRecordingButtonState:(TrackRecordingButtonState)state;
+
 #pragma mark - MWMNavigationDashboardManager
 
 - (void)onRoutePrepare;
diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm
index 8357f5e63e..703e2df9ea 100644
--- a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm
+++ b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm
@@ -28,15 +28,15 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
 
 @interface MWMMapViewControlsManager () <BottomMenuDelegate>
 
-@property(nonatomic) MWMSideButtons *sideButtons;
-@property(nonatomic) MWMTrafficButtonViewController *trafficButton;
-@property(nonatomic) UIButton *promoButton;
-@property(nonatomic) UIViewController *menuController;
+@property(nonatomic) MWMSideButtons * sideButtons;
+@property(nonatomic) MWMTrafficButtonViewController * trafficButton;
+@property(nonatomic) UIButton * promoButton;
+@property(nonatomic) UIViewController * menuController;
 @property(nonatomic) id<MWMPlacePageProtocol> placePageManager;
-@property(nonatomic) MWMNavigationDashboardManager *navigationManager;
-@property(nonatomic) SearchOnMapManager *searchManager;
+@property(nonatomic) MWMNavigationDashboardManager * navigationManager;
+@property(nonatomic) SearchOnMapManager * searchManager;
 
-@property(weak, nonatomic) MapViewController *ownerController;
+@property(weak, nonatomic) MapViewController * ownerController;
 
 @property(nonatomic) BOOL disableStandbyOnRouteFollowing;
 @property(nonatomic) BOOL isAddingPlace;
@@ -63,17 +63,10 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
   self.menuState = MWMBottomMenuStateInactive;
   self.menuRestoreState = MWMBottomMenuStateInactive;
   self.isAddingPlace = NO;
-  [TrackRecordingManager.shared addObserver:self recordingIsActiveDidChangeHandler:^(TrackRecordingState state, TrackInfo * trackInfo) {
-    [self setTrackRecordingButtonHidden:state == TrackRecordingStateInactive];
-  }];
   self.searchManager = controller.searchManager;
   return self;
 }
 
-- (void)dealloc {
-  [TrackRecordingManager.shared removeObserver:self];
-}
-
 - (UIStatusBarStyle)preferredStatusBarStyle {
   BOOL const isNavigationUnderStatusBar = self.navigationManager.state != MWMNavigationDashboardStateHidden &&
                                           self.navigationManager.state != MWMNavigationDashboardStateNavigation;
@@ -280,17 +273,15 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
   self.trafficButton.hidden = self.hidden || _trafficButtonHidden;
 }
 
-- (void)setTrackRecordingButtonHidden:(BOOL)trackRecordingButtonHidden {
-  if (trackRecordingButtonHidden && _trackRecordingButton) {
-    [self.trackRecordingButton closeWithCompletion:^{
-      [MWMMapWidgetsHelper updateLayoutForAvailableArea];
-    }];
-    _trackRecordingButton = nil;
+- (void)setTrackRecordingButtonState:(TrackRecordingButtonState)state {
+  if (!_trackRecordingButton) {
+    _trackRecordingButton = [[TrackRecordingButtonViewController alloc] init];
   }
-  else if (!trackRecordingButtonHidden && !_trackRecordingButton) {
-    _trackRecordingButton = [[TrackRecordingViewController alloc] init];
+  [self.trackRecordingButton setState:state completion:^{
     [MWMMapWidgetsHelper updateLayoutForAvailableArea];
-  }
+  }];
+  if (state == TrackRecordingButtonStateClosed)
+    _trackRecordingButton = nil;
 }
 
 - (void)setMenuState:(MWMBottomMenuState)menuState {
diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingViewController.swift b/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingButtonViewController.swift
similarity index 73%
rename from iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingViewController.swift
rename to iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingButtonViewController.swift
index a4385108aa..fd864d2b08 100644
--- a/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingViewController.swift
+++ b/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingButtonViewController.swift
@@ -1,4 +1,4 @@
-final class TrackRecordingViewController: MWMViewController {
+final class TrackRecordingButtonViewController: MWMViewController {
 
   private enum Constants {
     static let buttonDiameter = CGFloat(48)
@@ -13,6 +13,7 @@ final class TrackRecordingViewController: MWMViewController {
   private var blinkingTimer: Timer?
   private var topConstraint = NSLayoutConstraint()
   private var trailingConstraint = NSLayoutConstraint()
+  private var state: TrackRecordingButtonState = .hidden
 
   private static var availableArea: CGRect = .zero
   private static var topConstraintValue: CGFloat {
@@ -38,31 +39,29 @@ final class TrackRecordingViewController: MWMViewController {
     fatalError("init(coder:) has not been implemented")
   }
 
-  override func viewDidAppear(_ animated: Bool) {
-    super.viewDidAppear(animated)
-    UIView.transition(with: self.view,
-                      duration: kDefaultAnimationDuration,
-                      options: .transitionCrossDissolve,
-                      animations: {
-      self.button.isHidden = false
-    })
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    // async is for smoother appearance
+    DispatchQueue.main.asyncAfter(deadline: .now() + kDefaultAnimationDuration) {
+      self.setState(self.state, completion: nil)
+    }
   }
 
   // MARK: - Public methods
 
   @objc
-  func close(completion: @escaping (() -> Void)) {
-    stopTimer()
-    UIView.transition(with: self.view,
-                      duration: kDefaultAnimationDuration,
-                      options: .transitionCrossDissolve,
-                      animations: {
-      self.button.isHidden = true
-    }, completion: { _ in
-      self.removeFromParent()
-      self.view.removeFromSuperview()
-      completion()
-    })
+  func setState(_ state: TrackRecordingButtonState, completion: (() -> Void)?) {
+    self.state = state
+    switch state {
+    case .visible:
+      setHidden(false, completion: nil)
+    case .hidden:
+      setHidden(true, completion: completion)
+    case .closed:
+      close(completion: completion)
+    @unknown default:
+      fatalError()
+    }
   }
 
   // MARK: - Private methods
@@ -75,7 +74,7 @@ final class TrackRecordingViewController: MWMViewController {
     button.tintColor = Constants.color.darker
     button.translatesAutoresizingMaskIntoConstraints = false
     button.setImage(UIImage(resource: .icMenuBookmarkTrackRecording), for: .normal)
-    button.addTarget(self, action: #selector(onTrackRecordingButtonPressed), for: .touchUpInside)
+    button.addTarget(self, action: #selector(didTap), for: .touchUpInside)
     button.isHidden = true
   }
 
@@ -97,7 +96,7 @@ final class TrackRecordingViewController: MWMViewController {
   }
 
   private func updateLayout() {
-    guard let superview = self.view.superview else { return }
+    guard let superview = view.superview else { return }
     superview.animateConstraints {
       self.topConstraint.constant = Self.topConstraintValue
       self.trailingConstraint.constant = Self.trailingConstraintValue
@@ -123,23 +122,39 @@ final class TrackRecordingViewController: MWMViewController {
     blinkingTimer = nil
   }
 
+  private func setHidden(_ hidden: Bool, completion: (() -> Void)?) {
+    UIView.transition(with: self.view,
+                      duration: kDefaultAnimationDuration,
+                      options: .transitionCrossDissolve,
+                      animations: {
+      self.button.isHidden = hidden
+    }) { _ in
+      completion?()
+    }
+  }
+
+  private func close(completion: (() -> Void)?) {
+    stopTimer()
+    setHidden(true) { [weak self] in
+      guard let self else { return }
+      self.removeFromParent()
+      self.view.removeFromSuperview()
+      completion?()
+    }
+  }
+
   static func updateAvailableArea(_ frame: CGRect) {
     availableArea = frame
-    guard let controller = MapViewController.shared()?.controlsManager.trackRecordingButton else { return }
+    guard let button = MapViewController.shared()?.controlsManager.trackRecordingButton else { return }
     DispatchQueue.main.async {
-      controller.updateLayout()
+      button.updateLayout()
     }
   }
 
   // MARK: - Actions
 
   @objc
-  private func onTrackRecordingButtonPressed(_ sender: Any) {
-    switch trackRecordingManager.recordingState {
-    case .inactive:
-      trackRecordingManager.processAction(.start)
-    case .active:
-      trackRecordingManager.processAction(.stop)
-    }
+  private func didTap(_ sender: Any) {
+    MapViewController.shared()?.showTrackRecordingPlacePage()
   }
 }
diff --git a/iphone/Maps/Classes/MapViewController.h b/iphone/Maps/Classes/MapViewController.h
index c6c1a3e3d5..e45d05b848 100644
--- a/iphone/Maps/Classes/MapViewController.h
+++ b/iphone/Maps/Classes/MapViewController.h
@@ -33,6 +33,7 @@
 - (void)openFullPlaceDescriptionWithHtml:(NSString *_Nonnull)htmlString;
 - (void)searchText:(NSString *_Nonnull)text;
 - (void)openDrivingOptions;
+- (void)showTrackRecordingPlacePage;
 
 - (void)setPlacePageTopBound:(CGFloat)bound duration:(double)duration;
 
diff --git a/iphone/Maps/Classes/MapViewController.mm b/iphone/Maps/Classes/MapViewController.mm
index 93fc70cb37..00f0f76e9d 100644
--- a/iphone/Maps/Classes/MapViewController.mm
+++ b/iphone/Maps/Classes/MapViewController.mm
@@ -76,6 +76,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
 
 @property(nonatomic, readwrite) MWMMapViewControlsManager *controlsManager;
 @property(nonatomic, readwrite) SearchOnMapManager *searchManager;
+@property(nonatomic, readwrite) TrackRecordingManager *trackRecordingManager;
 
 @property(nonatomic) BOOL disableStandbyOnLocationStateMode;
 
@@ -116,7 +117,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
   return [MapsAppDelegate theApp].mapViewController;
 }
 
-#pragma mark - Map Navigation
+#pragma mark - PlacePage
 
 - (void)showOrUpdatePlacePage:(PlacePageData *)data {
   if (self.searchManager.isSearching)
@@ -124,9 +125,10 @@ NSString *const kSettingsSegue = @"Map2Settings";
 
   self.controlsManager.trafficButtonHidden = YES;
   if (self.placePageVC != nil) {
-    [PlacePageBuilder update:(PlacePageViewController *)self.placePageVC with:data];
+    [PlacePageBuilder update:self.placePageVC with:data];
     return;
   }
+
   [self showPlacePageFor:data];
 }
 
@@ -190,6 +192,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
 }
 
 - (void)hideRegularPlacePage {
+  [self stopObservingTrackRecordingUpdates];
   [self.placePageVC closeAnimatedWithCompletion:^{
     [self.placePageVC.view removeFromSuperview];
     [self.placePageVC willMoveToParentViewController:nil];
@@ -233,6 +236,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
     return;
   }
   PlacePageData * data = [[PlacePageData alloc] initWithLocalizationProvider:[[OpeinigHoursLocalization alloc] init]];
+  [self stopObservingTrackRecordingUpdates];
   [self showOrUpdatePlacePage:data];
 }
 
@@ -409,6 +413,9 @@ NSString *const kSettingsSegue = @"Map2Settings";
   // After all users migrate to OAuth2 we can remove next code
   [self migrateOAuthCredentials];
 
+  if (self.trackRecordingManager.isActive)
+    [self showTrackRecordingPlacePage];
+
   /// @todo: Uncomment update dialog when will be ready to handle big traffic bursts.
   /*
   if (!DeepLinkHandler.shared.isLaunchedByDeeplink)
@@ -730,6 +737,12 @@ NSString *const kSettingsSegue = @"Map2Settings";
   return _searchManager;
 }
 
+- (TrackRecordingManager *)trackRecordingManager {
+  if (!_trackRecordingManager)
+    _trackRecordingManager = TrackRecordingManager.shared;
+  return _trackRecordingManager;
+}
+
 - (UIView * _Nullable)searchViewContainer {
   return self.searchManager.viewController.view;
 }
@@ -849,6 +862,50 @@ NSString *const kSettingsSegue = @"Map2Settings";
   }
 }
 
+// MARK: - Track Recording Place Page
+
+- (void)showTrackRecordingPlacePage {
+  if ([self.trackRecordingManager contains:self]) {
+    [self dismissPlacePage];
+    return;
+  }
+  PlacePageData * placePageData = [[PlacePageData alloc] initWithTrackInfo:self.trackRecordingManager.trackRecordingInfo
+                                                             elevationInfo:self.trackRecordingManager.trackRecordingElevationProfileData];
+  [self.controlsManager setTrackRecordingButtonState:TrackRecordingButtonStateHidden];
+  [self showOrUpdatePlacePage:placePageData];
+  [self startObservingTrackRecordingUpdatesForPlacePageData:placePageData];
+}
+
+- (void)startObservingTrackRecordingUpdatesForPlacePageData:(PlacePageData *)placePageData {
+  __weak __typeof(self) weakSelf = self;
+  [self.trackRecordingManager addObserver:self
+        recordingIsActiveDidChangeHandler:^(TrackRecordingState state,
+                                            TrackInfo * _Nonnull trackInfo,
+                                            ElevationProfileData * _Nonnull (^ _Nullable elevationData) ()) {
+    __strong __typeof(weakSelf) self = weakSelf;
+
+    switch (state) {
+      case TrackRecordingStateInactive:
+        [self stopObservingTrackRecordingUpdates];
+        [self.controlsManager setTrackRecordingButtonState:TrackRecordingButtonStateClosed];
+        break;
+      case TrackRecordingStateActive:
+        if (UIApplication.sharedApplication.applicationState != UIApplicationStateActive)
+          return;
+        [self.controlsManager setTrackRecordingButtonState:TrackRecordingButtonStateHidden];
+        [placePageData updateWithTrackInfo:trackInfo
+                             elevationInfo:elevationData()];
+        break;
+    }
+  }];
+}
+
+- (void)stopObservingTrackRecordingUpdates {
+  [self.trackRecordingManager removeObserver:self];
+  if (self.trackRecordingManager.isActive)
+    [self.controlsManager setTrackRecordingButtonState:TrackRecordingButtonStateVisible];
+}
+
 // MARK: - Handle macOS trackpad gestures
 
 - (void)handlePan:(UIPanGestureRecognizer *)recognizer API_AVAILABLE(ios(14.0)) {
diff --git a/iphone/Maps/Core/Location/MWMLocationManager.h b/iphone/Maps/Core/Location/MWMLocationManager.h
index e662c47921..2bb3b033f7 100644
--- a/iphone/Maps/Core/Location/MWMLocationManager.h
+++ b/iphone/Maps/Core/Location/MWMLocationManager.h
@@ -3,8 +3,15 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
+@protocol LocationService
+
++ (BOOL)isLocationProhibited;
++ (void)checkLocationStatus;
+
+@end
+
 NS_SWIFT_NAME(LocationManager)
-@interface MWMLocationManager : NSObject
+@interface MWMLocationManager : NSObject<LocationService>
 
 + (void)start;
 + (void)stop;
@@ -14,10 +21,8 @@ NS_SWIFT_NAME(LocationManager)
 + (void)removeObserver:(id<MWMLocationObserver>)observer NS_SWIFT_NAME(remove(observer:));
 
 + (void)setMyPositionMode:(MWMMyPositionMode)mode;
-+ (void)checkLocationStatus;
 
 + (nullable CLLocation *)lastLocation;
-+ (BOOL)isLocationProhibited;
 + (nullable CLHeading *)lastHeading;
 
 + (void)applicationDidBecomeActive;
diff --git a/iphone/Maps/Core/Theme/GlobalStyleSheet.swift b/iphone/Maps/Core/Theme/GlobalStyleSheet.swift
index 60590d985b..e5bf69bc67 100644
--- a/iphone/Maps/Core/Theme/GlobalStyleSheet.swift
+++ b/iphone/Maps/Core/Theme/GlobalStyleSheet.swift
@@ -185,6 +185,7 @@ extension GlobalStyleSheet: IStyleSheet {
     case .trackRecordingWidgetButton:
       return .addFrom(Self.bottomTabBarButton) { s in
         s.cornerRadius = 23
+        s.coloring = .red
       }
     case .blackOpaqueBackground:
       return .add { s in
diff --git a/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift b/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift
index 5012b2ed65..0e4e72e57b 100644
--- a/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift
+++ b/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift
@@ -4,31 +4,47 @@ enum TrackRecordingState: Int, Equatable {
   case active
 }
 
-enum TrackRecordingAction: String, CaseIterable {
+enum TrackRecordingAction {
   case start
-  case stop
+  case stopAndSave(name: String)
 }
 
 enum TrackRecordingError: Error {
   case locationIsProhibited
+  case trackIsEmpty
+  case systemError(Error)
 }
 
-protocol TrackRecordingObserver: AnyObject {
+enum TrackRecordingActionResult {
+  case success
+  case error(TrackRecordingError)
+}
+
+@objc
+protocol TrackRecordingObservable: AnyObject {
+  var recordingState: TrackRecordingState { get }
+  var trackRecordingInfo: TrackInfo { get }
+  var trackRecordingElevationProfileData: ElevationProfileData { get }
+
   func addObserver(_ observer: AnyObject, recordingIsActiveDidChangeHandler: @escaping TrackRecordingStateHandler)
   func removeObserver(_ observer: AnyObject)
+  func contains(_ observer: AnyObject) -> Bool
 }
 
-typealias TrackRecordingStateHandler = (TrackRecordingState, TrackInfo?) -> Void
+/// A handler type for extracting elevation profile data on demand.
+typealias ElevationProfileDataExtractionHandler = () -> ElevationProfileData
+
+/// A callback type that notifies observers about track recording state changes.
+/// - Parameters:
+///   - state: The current recording state.
+///   - info: The current track recording info.
+///   - elevationProfileExtractor: A closure to fetch elevation profile data lazily.
+typealias TrackRecordingStateHandler = (TrackRecordingState, TrackInfo, ElevationProfileDataExtractionHandler?) -> Void
 
 @objcMembers
 final class TrackRecordingManager: NSObject {
 
-  typealias CompletionHandler = () -> Void
-
-  private enum SavingOption {
-    case withoutSaving
-    case saveWithName(String? = nil)
-  }
+  typealias CompletionHandler = (TrackRecordingActionResult) -> Void
 
   fileprivate struct Observation {
     weak var observer: AnyObject?
@@ -37,28 +53,40 @@ final class TrackRecordingManager: NSObject {
 
   static let shared: TrackRecordingManager = {
     let trackRecorder = FrameworkHelper.self
+    let locationManager = LocationManager.self
     var activityManager: TrackRecordingActivityManager? = nil
     #if canImport(ActivityKit)
     if #available(iOS 16.2, *) {
       activityManager = TrackRecordingLiveActivityManager.shared
     }
     #endif
-    return TrackRecordingManager(trackRecorder: trackRecorder, activityManager: activityManager)
+    return TrackRecordingManager(trackRecorder: trackRecorder,
+                                 locationService: locationManager,
+                                 activityManager: activityManager)
   }()
 
   private let trackRecorder: TrackRecorder.Type
+  private var locationService: LocationService.Type
   private var activityManager: TrackRecordingActivityManager?
   private var observations: [Observation] = []
-  private var trackRecordingInfo: TrackInfo?
+  private(set) var trackRecordingInfo: TrackInfo = .empty()
+
+  var trackRecordingElevationProfileData: ElevationProfileData {
+    FrameworkHelper.trackRecordingElevationInfo()
+  }
 
   var recordingState: TrackRecordingState {
     trackRecorder.isTrackRecordingEnabled() ? .active : .inactive
   }
 
-  private init(trackRecorder: TrackRecorder.Type, activityManager: TrackRecordingActivityManager?) {
+  init(trackRecorder: TrackRecorder.Type,
+       locationService: LocationService.Type,
+       activityManager: TrackRecordingActivityManager?) {
     self.trackRecorder = trackRecorder
+    self.locationService = locationService
     self.activityManager = activityManager
     super.init()
+    self.subscribeOnTheAppLifecycleEvents()
   }
 
   // MARK: - Public methods
@@ -84,22 +112,42 @@ final class TrackRecordingManager: NSObject {
   }
 
   func processAction(_ action: TrackRecordingAction, completion: (CompletionHandler)? = nil) {
-    switch action {
-    case .start:
-      start(completion: completion)
-    case .stop:
-      stop(completion: completion)
+    do {
+      switch action {
+      case .start:
+        try startRecording()
+      case .stopAndSave(let name):
+        stopRecording()
+        try checkIsTrackNotEmpty()
+        saveTrackRecording(name: name)
+      }
+      completion?(.success)
+    } catch {
+      handleError(error, completion: completion)
     }
   }
 
   // MARK: - Private methods
 
-  private func checkIsLocationEnabled() throws {
-    if LocationManager.isLocationProhibited() {
+  private func subscribeOnTheAppLifecycleEvents() {
+    NotificationCenter.default.addObserver(self,
+                                           selector: #selector(notifyObservers),
+                                           name: UIApplication.didBecomeActiveNotification,
+                                           object: nil)
+  }
+
+  private func checkIsLocationEnabled() throws(TrackRecordingError) {
+    if locationService.isLocationProhibited() {
       throw TrackRecordingError.locationIsProhibited
     }
   }
 
+  private func checkIsTrackNotEmpty() throws(TrackRecordingError) {
+    if trackRecorder.isTrackRecordingEmpty() {
+      throw TrackRecordingError.trackIsEmpty
+    }
+  }
+
   // MARK: - Handle track recording process
 
   private func subscribeOnTrackRecordingProgressUpdates() {
@@ -113,95 +161,68 @@ final class TrackRecordingManager: NSObject {
 
   private func unsubscribeFromTrackRecordingProgressUpdates() {
     trackRecorder.setTrackRecordingUpdateHandler(nil)
-    trackRecordingInfo = nil
   }
 
   // MARK: - Handle Start/Stop event and Errors
 
-  private func start(completion: (CompletionHandler)? = nil) {
-    do {
+  private func startRecording() throws(TrackRecordingError) {
+    switch recordingState {
+    case .inactive:
       try checkIsLocationEnabled()
-      switch recordingState {
-      case .inactive:
-        subscribeOnTrackRecordingProgressUpdates()
-        trackRecorder.startTrackRecording()
-        notifyObservers()
-        try? activityManager?.start(with: trackRecordingInfo ?? .empty())
-      case .active:
-        break
+      subscribeOnTrackRecordingProgressUpdates()
+      trackRecorder.startTrackRecording()
+      notifyObservers()
+      do {
+        try activityManager?.start(with: trackRecordingInfo)
+      } catch {
+        LOG(.warning, "Failed to start activity manager")
+        handleError(.systemError(error))
       }
-      completion?()
-    } catch {
-      handleError(error, completion: completion)
+    case .active:
+      break
     }
   }
 
-  private func stop(completion: (CompletionHandler)? = nil) {
-    guard !trackRecorder.isTrackRecordingEmpty() else {
-      Toast.toast(withText: L("track_recording_toast_nothing_to_save")).show()
-      stopRecording(.withoutSaving, completion: completion)
-      return
-    }
-    Self.showOnFinishRecordingAlert(onSave: { [weak self] in
-      guard let self else { return }
-      // TODO: (KK) pass the user provided name from the track saving screen (when it will be implemented)
-      self.stopRecording(.saveWithName(), completion: completion)
-    },
-                                    onStop: { [weak self] in
-      guard let self else { return }
-      self.stopRecording(.withoutSaving, completion: completion)
-    },
-                                    onContinue: {
-      completion?()
-    })
-  }
-
-  private func stopRecording(_ savingOption: SavingOption, completion: (CompletionHandler)? = nil) {
+  private func stopRecording() {
     unsubscribeFromTrackRecordingProgressUpdates()
     trackRecorder.stopTrackRecording()
+    trackRecordingInfo = .empty()
     activityManager?.stop()
     notifyObservers()
-    
-    switch savingOption {
-    case .withoutSaving:
-      break
-    case .saveWithName(let name):
-      trackRecorder.saveTrackRecording(withName: name)
-    }
-    completion?()
   }
 
-  private func handleError(_ error: Error, completion: (CompletionHandler)? = nil) {
-    LOG(.error, error.localizedDescription)
+  private func saveTrackRecording(name: String) {
+    trackRecorder.saveTrackRecording(withName: name)
+  }
+
+  private func handleError(_ error: TrackRecordingError, completion: (CompletionHandler)? = nil) {
     switch error {
     case TrackRecordingError.locationIsProhibited:
       // Show alert to enable location
-      LocationManager.checkLocationStatus()
-    default:
+      locationService.checkLocationStatus()
+    case TrackRecordingError.trackIsEmpty:
+      Toast.toast(withText: L("track_recording_toast_nothing_to_save")).show()
+    case TrackRecordingError.systemError(let error):
+      LOG(.error, error.localizedDescription)
       break
     }
-    stopRecording(.withoutSaving, completion: completion)
-  }
-
-  private static func showOnFinishRecordingAlert(onSave: @escaping CompletionHandler,
-                                                 onStop: @escaping CompletionHandler,
-                                                 onContinue: @escaping CompletionHandler) {
-    let alert = UIAlertController(title: L("track_recording_alert_title"), message: nil, preferredStyle: .alert)
-    alert.addAction(UIAlertAction(title: L("continue_recording"), style: .default, handler: { _ in onContinue() }))
-    alert.addAction(UIAlertAction(title: L("stop_without_saving"), style: .default, handler: { _ in onStop() }))
-    alert.addAction(UIAlertAction(title: L("save"), style: .cancel, handler: { _ in onSave() }))
-    UIViewController.topViewController().present(alert, animated: true)
+    DispatchQueue.main.async {
+      completion?(.error(error))
+    }
   }
 }
 
 // MARK: - TrackRecordingObserver
 
-extension TrackRecordingManager: TrackRecordingObserver {
+extension TrackRecordingManager: TrackRecordingObservable {
   @objc
   func addObserver(_ observer: AnyObject, recordingIsActiveDidChangeHandler: @escaping TrackRecordingStateHandler) {
+    guard !observations.contains(where: { $0.observer === observer }) else { return }
     let observation = Observation(observer: observer, recordingStateDidChangeHandler: recordingIsActiveDidChangeHandler)
     observations.append(observation)
-    recordingIsActiveDidChangeHandler(recordingState, trackRecordingInfo)
+    recordingIsActiveDidChangeHandler(recordingState, trackRecordingInfo) {
+      self.trackRecordingElevationProfileData
+    }
   }
 
   @objc
@@ -209,8 +230,16 @@ extension TrackRecordingManager: TrackRecordingObserver {
     observations.removeAll { $0.observer === observer }
   }
 
+  @objc
+  func contains(_ observer: AnyObject) -> Bool {
+    observations.contains { $0.observer === observer }
+  }
+
+  @objc
   private func notifyObservers() {
-    observations = observations.filter { $0.observer != nil }
-    observations.forEach { $0.recordingStateDidChangeHandler?(recordingState, trackRecordingInfo) }
+    observations.removeAll { $0.observer == nil }
+    observations.forEach {
+      $0.recordingStateDidChangeHandler?(recordingState, trackRecordingInfo, { self.trackRecordingElevationProfileData })
+    }
   }
 }
diff --git a/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/Contents.json b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/Contents.json
new file mode 100644
index 0000000000..4c2879a332
--- /dev/null
+++ b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/Contents.json	
@@ -0,0 +1,15 @@
+{
+  "images" : [
+    {
+      "filename" : "ic_track_save.png",
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "template-rendering-intent" : "template"
+  }
+}
diff --git a/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/ic_track_save.png b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/ic_track_save.png
new file mode 100644
index 0000000000..d0bee6031e
Binary files /dev/null and b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/ic_track_save.png differ
diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj
index 1023b1e8e8..0cf4419213 100644
--- a/iphone/Maps/Maps.xcodeproj/project.pbxproj
+++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj
@@ -481,7 +481,7 @@
 		ED2D74652D14357F00660FBF /* TrackRecordingLiveActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2D74302D14337500660FBF /* TrackRecordingLiveActivityAttributes.swift */; };
 		ED2D74662D1435A600660FBF /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2D742D2D14337500660FBF /* LiveActivityManager.swift */; };
 		ED2E328E2D10500900807A08 /* TrackRecordingButtonArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED46DD922D06F804007CACD6 /* TrackRecordingButtonArea.swift */; };
-		ED2E32912D10501700807A08 /* TrackRecordingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED49D76F2CF0E3A8004AF27E /* TrackRecordingViewController.swift */; };
+		ED2E32912D10501700807A08 /* TrackRecordingButtonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED49D76F2CF0E3A8004AF27E /* TrackRecordingButtonViewController.swift */; };
 		ED3EAC202B03C88100220A4A /* BottomTabBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED3EAC1F2B03C88100220A4A /* BottomTabBarButton.swift */; };
 		ED43B8BD2C12063500D07BAA /* DocumentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED43B8BC2C12063500D07BAA /* DocumentPicker.swift */; };
 		ED46DDCE2D098A0B007CACD6 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED46DDCD2D098A0B007CACD6 /* WidgetKit.framework */; };
@@ -524,6 +524,8 @@
 		ED914ABE2D351FF800973C45 /* UILabel+SetFontStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED914ABD2D351FF800973C45 /* UILabel+SetFontStyle.swift */; };
 		ED9857082C4ED02D00694F6C /* MailComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED9857072C4ED02D00694F6C /* MailComposer.swift */; };
 		ED9966802B94FBC20083CE55 /* ColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED99667D2B94FBC20083CE55 /* ColorPicker.swift */; };
+		ED9DDF882D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED9DDF872D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift */; };
+		ED9DDF9D2D6F6F7900645BC8 /* TrackRecordingManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED9DDF9B2D6F6DA300645BC8 /* TrackRecordingManagerTests.swift */; };
 		EDA1EAA42CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDA1EAA32CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift */; };
 		EDBD68072B625724005DD151 /* LocationServicesDisabledAlert.xib in Resources */ = {isa = PBXBuildFile; fileRef = EDBD68062B625724005DD151 /* LocationServicesDisabledAlert.xib */; };
 		EDBD680B2B62572E005DD151 /* LocationServicesDisabledAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDBD680A2B62572E005DD151 /* LocationServicesDisabledAlert.swift */; };
@@ -1449,7 +1451,7 @@
 		ED46DDCF2D098A0B007CACD6 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
 		ED48BBB817C2B1E2003E7E92 /* CircleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CircleView.h; sourceTree = "<group>"; };
 		ED48BBB917C2B1E2003E7E92 /* CircleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CircleView.m; sourceTree = "<group>"; };
-		ED49D76F2CF0E3A8004AF27E /* TrackRecordingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackRecordingViewController.swift; sourceTree = "<group>"; };
+		ED49D76F2CF0E3A8004AF27E /* TrackRecordingButtonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackRecordingButtonViewController.swift; sourceTree = "<group>"; };
 		ED4DC7732CAEDECC0029B338 /* ProductButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductButton.swift; sourceTree = "<group>"; };
 		ED4DC7742CAEDECC0029B338 /* ProductsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewController.swift; sourceTree = "<group>"; };
 		ED4DC7752CAEDECC0029B338 /* ProductsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewModel.swift; sourceTree = "<group>"; };
@@ -1490,6 +1492,8 @@
 		ED914ABD2D351FF800973C45 /* UILabel+SetFontStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+SetFontStyle.swift"; sourceTree = "<group>"; };
 		ED9857072C4ED02D00694F6C /* MailComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposer.swift; sourceTree = "<group>"; };
 		ED99667D2B94FBC20083CE55 /* ColorPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPicker.swift; sourceTree = "<group>"; };
+		ED9DDF872D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlacePageTrackRecordingLayout.swift; sourceTree = "<group>"; };
+		ED9DDF9B2D6F6DA300645BC8 /* TrackRecordingManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackRecordingManagerTests.swift; sourceTree = "<group>"; };
 		EDA1EAA32CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElevationProfileFormatter.swift; sourceTree = "<group>"; };
 		EDBD68062B625724005DD151 /* LocationServicesDisabledAlert.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LocationServicesDisabledAlert.xib; sourceTree = "<group>"; };
 		EDBD680A2B62572E005DD151 /* LocationServicesDisabledAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationServicesDisabledAlert.swift; sourceTree = "<group>"; };
@@ -2569,7 +2573,7 @@
 		34BC72091B0DECAE0012A34B /* MapViewControls */ = {
 			isa = PBXGroup;
 			children = (
-				ED49D76F2CF0E3A8004AF27E /* TrackRecordingViewController.swift */,
+				ED49D76F2CF0E3A8004AF27E /* TrackRecordingButtonViewController.swift */,
 				340537621BBED98600D452C6 /* MWMMapViewControlsCommon.h */,
 				34BC72101B0DECAE0012A34B /* MWMMapViewControlsManager.h */,
 				34BC72111B0DECAE0012A34B /* MWMMapViewControlsManager.mm */,
@@ -2765,6 +2769,7 @@
 		4B4153B62BF9709100EE4B02 /* Core */ = {
 			isa = PBXGroup;
 			children = (
+				ED9DDF982D6F6D8000645BC8 /* TrackRecorder */,
 				EDF838AB2C00B9C7007E4E67 /* iCloudTests */,
 				4B4153B72BF970A000EE4B02 /* TextToSpeech */,
 			);
@@ -3031,6 +3036,7 @@
 				99C6532123F2F506004322F3 /* IPlacePageLayout.swift */,
 				99F3EB0223F4178200C713F8 /* PlacePageCommonLayout.swift */,
 				993DF0B423F6B2EF00AC231A /* PlacePageTrackLayout.swift */,
+				ED9DDF872D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift */,
 			);
 			path = Layouts;
 			sourceTree = "<group>";
@@ -3312,6 +3318,14 @@
 			path = ColorPicker;
 			sourceTree = "<group>";
 		};
+		ED9DDF982D6F6D8000645BC8 /* TrackRecorder */ = {
+			isa = PBXGroup;
+			children = (
+				ED9DDF9B2D6F6DA300645BC8 /* TrackRecordingManagerTests.swift */,
+			);
+			path = TrackRecorder;
+			sourceTree = "<group>";
+		};
 		EDC4E3422C5D1BD3009286A2 /* RecentlyDeletedTests */ = {
 			isa = PBXGroup;
 			children = (
@@ -4489,7 +4503,7 @@
 				340708651F2905A500029ECC /* NavigationInfoArea.swift in Sources */,
 				993DF0CC23F6BD0600AC231A /* ElevationDetailsPresenter.swift in Sources */,
 				34AB666B1FC5AA330078E451 /* TransportTransitCell.swift in Sources */,
-				ED2E32912D10501700807A08 /* TrackRecordingViewController.swift in Sources */,
+				ED2E32912D10501700807A08 /* TrackRecordingButtonViewController.swift in Sources */,
 				47E8163323B17734008FD836 /* MWMStorage+UI.m in Sources */,
 				993DF11123F6BDB100AC231A /* UILabelRenderer.swift in Sources */,
 				34AB66471FC5AA330078E451 /* RouteManagerTableView.swift in Sources */,
@@ -4630,6 +4644,7 @@
 				1DFA2F6A20D3B57400FB2C66 /* UIColor+PartnerColor.m in Sources */,
 				9989273B2449E60200260CE2 /* BottomMenuBuilder.swift in Sources */,
 				993DF10F23F6BDB100AC231A /* UIActivityIndicatorRenderer.swift in Sources */,
+				ED9DDF882D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift in Sources */,
 				ED0B1FEF2CAA9A25006E31A4 /* UIView+Highlight.swift in Sources */,
 				99A614E423CDD1D900D8D8D0 /* UIButton+RuntimeAttributes.m in Sources */,
 				343E75981E5B1EE20041226A /* MWMCollectionViewController.m in Sources */,
@@ -4907,6 +4922,7 @@
 				EDF838C32C00B9D6007E4E67 /* UbiquitousDirectoryMonitorDelegateMock.swift in Sources */,
 				EDF838BE2C00B9D0007E4E67 /* LocalDirectoryMonitorDelegateMock.swift in Sources */,
 				EDC4E3692C5E6F5B009286A2 /* MockRecentlyDeletedCategoriesManager.swift in Sources */,
+				ED9DDF9D2D6F6F7900645BC8 /* TrackRecordingManagerTests.swift in Sources */,
 				EDF838BF2C00B9D0007E4E67 /* SynchronizationStateManagerTests.swift in Sources */,
 				ED810EC52D566E9B00ECDE2C /* SearchOnMapTests.swift in Sources */,
 				4B83AE4B2C2E642100B0C3BC /* TTSTesterTest.m in Sources */,
diff --git a/iphone/Maps/Tests/Core/TrackRecorder/TrackRecordingManagerTests.swift b/iphone/Maps/Tests/Core/TrackRecorder/TrackRecordingManagerTests.swift
new file mode 100644
index 0000000000..ecb44380d2
--- /dev/null
+++ b/iphone/Maps/Tests/Core/TrackRecorder/TrackRecordingManagerTests.swift
@@ -0,0 +1,198 @@
+import XCTest
+@testable import Organic_Maps__Debug_
+
+final class TrackRecordingManagerTests: XCTestCase {
+
+  private var trackRecordingManager: TrackRecordingManager!
+
+  private var mockTrackRecorder: MockTrackRecorder.Type!
+  private var mockLocationService: MockLocationService.Type!
+  private var mockActivityManager: MockTrackRecordingActivityManager!
+
+  override func setUp() {
+    super.setUp()
+    mockTrackRecorder = MockTrackRecorder.self
+    mockLocationService = MockLocationService.self
+    mockActivityManager = MockTrackRecordingActivityManager()
+
+    trackRecordingManager = TrackRecordingManager(
+      trackRecorder: mockTrackRecorder,
+      locationService: mockLocationService,
+      activityManager: mockActivityManager
+    )
+  }
+
+  override func tearDown() {
+    trackRecordingManager = nil
+    mockTrackRecorder.reset()
+    mockLocationService.reset()
+    mockActivityManager = nil
+    super.tearDown()
+  }
+
+  func test_GivenInitialSetup_WhenLocationEnabled_ThenStateIsInactive() {
+    mockLocationService.locationIsProhibited = false
+    mockTrackRecorder.trackRecordingIsEnabled = false
+
+    trackRecordingManager.setup()
+    XCTAssertTrue(trackRecordingManager.recordingState == .inactive)
+  }
+
+  func test_GivenInitialSetup_WhenLocationDisabled_ThenShouldHandleErrorAndIncativeState() {
+    mockLocationService.locationIsProhibited = true
+
+    trackRecordingManager.setup()
+
+    XCTAssertTrue(mockLocationService.checkLocationStatusCalled)
+    XCTAssertTrue(trackRecordingManager.recordingState == .inactive)
+  }
+
+  func test_GivenStartRecording_WhenLocationEnabled_ThenSuccess() {
+    mockLocationService.locationIsProhibited = false
+    mockTrackRecorder.trackRecordingIsEnabled = false
+
+    trackRecordingManager.processAction(.start)
+
+    XCTAssertTrue(mockTrackRecorder.startTrackRecordingCalled)
+    XCTAssertTrue(mockActivityManager.startCalled)
+    XCTAssertTrue(trackRecordingManager.recordingState == .active)
+  }
+
+  func test_GivenStartRecording_WhenLocationDisabled_ThenShouldFail() {
+    mockLocationService.locationIsProhibited = true
+
+    trackRecordingManager.processAction(.start) { result in
+      switch result {
+      case .success:
+        XCTFail("Should not succeed")
+      case .error(let error):
+        switch error {
+        case .locationIsProhibited:
+          XCTAssertTrue(true)
+        default:
+          XCTFail("Unexpected error: \(error)")
+        }
+      }
+    }
+    XCTAssertFalse(self.mockTrackRecorder.startTrackRecordingCalled)
+    XCTAssertTrue(trackRecordingManager.recordingState == .inactive)
+  }
+
+  func test_GivenStopRecording_WhenLocationEnabled_ThenSuccess() {
+    mockTrackRecorder.trackRecordingIsEnabled = true
+    mockTrackRecorder.trackRecordingIsEmpty = false
+
+    trackRecordingManager.processAction(.stopAndSave(name: "Test Track")) { result in
+      switch result {
+      case .success:
+        XCTAssertTrue(true)
+      case .error(let error):
+        XCTFail("Unexpected error: \(error)")
+      }
+    }
+    XCTAssertTrue(mockTrackRecorder.stopTrackRecordingCalled)
+    XCTAssertTrue(mockTrackRecorder.saveTrackRecordingCalled)
+    XCTAssertTrue(mockActivityManager.stopCalled)
+    XCTAssertTrue(trackRecordingManager.recordingState == .inactive)
+  }
+
+  func test_GivenStopRecording_WhenTrackIsEmpty_ThenShouldFail() {
+    mockTrackRecorder.trackRecordingIsEnabled = true
+    mockTrackRecorder.trackRecordingIsEmpty = true
+
+    trackRecordingManager.processAction(.stopAndSave(name: "Test Track")) { result in
+      switch result {
+      case .success:
+        XCTFail("Should not succeed")
+      case .error(let error):
+        switch error {
+        case .trackIsEmpty:
+          XCTAssertTrue(true)
+        default:
+          XCTFail("Unexpected error: \(error)")
+        }
+      }
+    }
+    XCTAssertFalse(mockTrackRecorder.saveTrackRecordingCalled)
+    XCTAssertTrue(trackRecordingManager.recordingState == .inactive)
+  }
+}
+
+// MARK: - Mock Classes
+
+private final class MockTrackRecorder: TrackRecorder {
+  static var trackRecordingIsEnabled = false
+  static var trackRecordingIsEmpty = false
+  static var startTrackRecordingCalled = false
+  static var stopTrackRecordingCalled = false
+  static var saveTrackRecordingCalled = false
+
+  static func reset() {
+    trackRecordingIsEnabled = false
+    trackRecordingIsEmpty = false
+    startTrackRecordingCalled = false
+    stopTrackRecordingCalled = false
+    saveTrackRecordingCalled = false
+  }
+
+  static func isTrackRecordingEnabled() -> Bool {
+    return trackRecordingIsEnabled
+  }
+
+  static func isTrackRecordingEmpty() -> Bool {
+    return trackRecordingIsEmpty
+  }
+
+  static func startTrackRecording() {
+    startTrackRecordingCalled = true
+    trackRecordingIsEnabled = true
+  }
+
+  static func stopTrackRecording() {
+    stopTrackRecordingCalled = true
+    trackRecordingIsEnabled = false
+  }
+
+  static func saveTrackRecording(withName name: String) {
+    saveTrackRecordingCalled = true
+  }
+
+  static func setTrackRecordingUpdateHandler(_ handler: ((TrackInfo) -> Void)?) {}
+
+  static func trackRecordingElevationInfo() -> ElevationProfileData {
+    ElevationProfileData()
+  }
+}
+
+private final class MockLocationService: LocationService {
+  static var locationIsProhibited = false
+  static var checkLocationStatusCalled = false
+
+  static func reset() {
+    locationIsProhibited = false
+    checkLocationStatusCalled = false
+  }
+
+  static func isLocationProhibited() -> Bool {
+    return locationIsProhibited
+  }
+
+  static func checkLocationStatus() {
+    checkLocationStatusCalled = true
+  }
+}
+
+final class MockTrackRecordingActivityManager: TrackRecordingActivityManager {
+  var startCalled = false
+  var stopCalled = false
+
+  func start(with info: TrackInfo) throws {
+    startCalled = true
+  }
+
+  func stop() {
+    stopCalled = true
+  }
+
+  func update(_ info: TrackInfo) {}
+}
diff --git a/iphone/Maps/UI/AvailableArea/TrackRecordingButtonArea.swift b/iphone/Maps/UI/AvailableArea/TrackRecordingButtonArea.swift
index 5272dd4931..9f1ca5e4a2 100644
--- a/iphone/Maps/UI/AvailableArea/TrackRecordingButtonArea.swift
+++ b/iphone/Maps/UI/AvailableArea/TrackRecordingButtonArea.swift
@@ -10,7 +10,7 @@ final class TrackRecordingButtonArea: AvailableArea {
   }
 
   override func notifyObserver() {
-    TrackRecordingViewController.updateAvailableArea(areaFrame)
+    TrackRecordingButtonViewController.updateAvailableArea(areaFrame)
   }
 }
 
diff --git a/iphone/Maps/UI/BottomMenu/Menu/BottomMenuInteractor.swift b/iphone/Maps/UI/BottomMenu/Menu/BottomMenuInteractor.swift
index 8457445c3c..d761d01e18 100644
--- a/iphone/Maps/UI/BottomMenu/Menu/BottomMenuInteractor.swift
+++ b/iphone/Maps/UI/BottomMenu/Menu/BottomMenuInteractor.swift
@@ -79,8 +79,13 @@ extension BottomMenuInteractor: BottomMenuInteractorProtocol {
   }
 
   func toggleTrackRecording() {
-    trackRecorder.processAction(trackRecorder.recordingState == .active ? .stop : .start) { [weak self] in
-      self?.close()
+    switch trackRecorder.recordingState {
+    case .active:
+      break
+    case .inactive:
+      trackRecorder.processAction(.start)
     }
+    close()
+    MapViewController.shared()?.showTrackRecordingPlacePage()
   }
 }
diff --git a/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift b/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift
index 1954964d90..f88c528079 100644
--- a/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift
+++ b/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift
@@ -62,18 +62,26 @@ final class ActionBarViewController: UIViewController {
         fatalError()
       }
     }
+
     var buttons: [ActionBarButtonType] = []
-    if isRoutePlanning {
-      buttons.append(.routeFrom)
-    }
-    if placePageData.infoData?.phone != nil, AppInfo.shared().canMakeCalls {
-      buttons.append(.call)
-    }
-    if !isRoutePlanning {
-      buttons.append(.routeFrom)
+    switch placePageData.objectType {
+    case .POI, .bookmark, .track:
+      if isRoutePlanning {
+        buttons.append(.routeFrom)
+      }
+      if placePageData.infoData?.phone != nil, AppInfo.shared().canMakeCalls {
+        buttons.append(.call)
+      }
+      if !isRoutePlanning {
+        buttons.append(.routeFrom)
+      }
+    case .trackRecording:
+      break
+    @unknown default:
+      fatalError()
     }
 
-    assert(buttons.count > 0)
+    guard !buttons.isEmpty else { return }
     visibleButtons.append(buttons[0])
     if buttons.count > 1 {
       additionalButtons.append(contentsOf: buttons.suffix(from: 1))
@@ -91,8 +99,7 @@ final class ActionBarViewController: UIViewController {
     case .track:
       buttons.append(.track)
     case .trackRecording:
-      // TODO: implement for track recording
-      break
+      buttons.append(.saveTrackRecording)
     @unknown default:
       fatalError()
     }
@@ -104,7 +111,14 @@ final class ActionBarViewController: UIViewController {
   }
 
   private func configButton3() {
-    visibleButtons.append(.routeTo)
+    switch placePageData.objectType {
+    case .POI, .bookmark, .track:
+      visibleButtons.append(.routeTo)
+    case .trackRecording:
+      break
+    @unknown default:
+      fatalError()
+    }
   }
 
   private func configButton4() {
diff --git a/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfileFormatter.swift b/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfileFormatter.swift
index 4a17c5c4c5..a6dbab87a8 100644
--- a/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfileFormatter.swift
+++ b/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfileFormatter.swift
@@ -5,7 +5,7 @@ final class ElevationProfileFormatter {
 
   private enum Constants {
     static let metricToImperialMultiplier: CGFloat = 0.3048
-    static var metricAltitudeStep: CGFloat = 50
+    static var metricAltitudeStep: CGFloat = 25
     static var imperialAltitudeStep: CGFloat = 100
   }
 
diff --git a/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfilePresenter.swift b/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfilePresenter.swift
index 585ae633a5..d4540645f7 100644
--- a/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfilePresenter.swift
+++ b/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfilePresenter.swift
@@ -85,6 +85,11 @@ extension ElevationProfilePresenter: ElevationProfilePresenterProtocol {
     view?.setChartData(ChartPresentationData(chartData, formatter: formatter))
     view?.reloadDescription()
 
+    guard !profileData.isTrackRecording else {
+      view?.isChartViewInfoHidden = true
+      return
+    }
+
     view?.setActivePoint(profileData.activePoint)
     view?.setMyPosition(profileData.myPosition)
     bookmarkManager.setElevationActivePointChanged(profileData.trackId) { [weak self] distance in
diff --git a/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderPresenter.swift b/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderPresenter.swift
index 8278ae5e67..daf2b7f323 100644
--- a/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderPresenter.swift
+++ b/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderPresenter.swift
@@ -51,8 +51,6 @@ extension PlacePageHeaderPresenter: PlacePageHeaderPresenterProtocol {
       view?.isExpandViewHidden = true
       view?.isShadowViewHidden = false
     }
-    // TODO: (KK) Enable share button for the tracks to share the whole track gpx/kml
-    view?.isShareButtonHidden = false
   }
 
   func onClosePress() {
diff --git a/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderViewController.swift b/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderViewController.swift
index 04c05e7a76..b77bfda011 100644
--- a/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderViewController.swift
+++ b/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderViewController.swift
@@ -2,7 +2,6 @@ protocol PlacePageHeaderViewProtocol: AnyObject {
   var presenter: PlacePageHeaderPresenterProtocol?  { get set }
   var isExpandViewHidden: Bool { get set }
   var isShadowViewHidden: Bool { get set }
-  var isShareButtonHidden: Bool { get set }
 
   func setTitle(_ title: String?, secondaryTitle: String?)
   func showShareTrackMenu()
@@ -78,15 +77,6 @@ extension PlacePageHeaderViewController: PlacePageHeaderViewProtocol {
     }
   }
 
-  var isShareButtonHidden: Bool {
-    get {
-      shareButton.isHidden
-    }
-    set {
-      shareButton.isHidden = newValue
-    }
-  }
-
   func setTitle(_ title: String?, secondaryTitle: String?) {
     titleText = title
     secondaryText = secondaryTitle
diff --git a/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift b/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift
index cac8307f61..8502a0d496 100644
--- a/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift
+++ b/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift
@@ -15,8 +15,7 @@
     case .track:
       layout = PlacePageTrackLayout(interactor: interactor, storyboard: storyboard, data: data)
     case .trackRecording:
-      // TODO: Implement PlacePageTrackRecordingLayout
-      fatalError("PlacePageTrackRecordingLayout is not implemented")
+      layout = PlacePageTrackRecordingLayout(interactor: interactor, storyboard: storyboard, data: data)
     @unknown default:
       fatalError()
     }
@@ -34,14 +33,14 @@
                                          data: data,
                                          mapViewController: MapViewController.shared()!)
     let layout: IPlacePageLayout
+    let storyboard = viewController.storyboard!
     switch data.objectType {
     case .POI, .bookmark:
-      layout = PlacePageCommonLayout(interactor: interactor, storyboard: viewController.storyboard!, data: data)
+      layout = PlacePageCommonLayout(interactor: interactor, storyboard: storyboard, data: data)
     case .track:
-      layout = PlacePageTrackLayout(interactor: interactor, storyboard: viewController.storyboard!, data: data)
+      layout = PlacePageTrackLayout(interactor: interactor, storyboard: storyboard, data: data)
     case .trackRecording:
-      // TODO: Implement PlacePageTrackRecordingLayout
-      fatalError("PlacePageTrackRecordingLayout is not implemented")
+      layout = PlacePageTrackRecordingLayout(interactor: interactor, storyboard: storyboard, data: data)
     @unknown default:
       fatalError()
     }
diff --git a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift
index bb2e8161b3..28a1ebb2a1 100644
--- a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift
+++ b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift
@@ -24,14 +24,29 @@ class PlacePageInteractor: NSObject {
   }
 
   private func updatePlacePageIfNeeded() {
-    let isBookmark = placePageData.bookmarkData != nil && bookmarksManager.hasBookmark(placePageData.bookmarkData!.bookmarkId)
-    let isTrack = placePageData.trackData != nil && bookmarksManager.hasTrack(placePageData.trackData!.trackId)
-    guard isBookmark || isTrack else {
-      presenter?.closeAnimated()
-      return
+    func updatePlacePage() {
+      FrameworkHelper.updatePlacePageData()
+      placePageData.updateBookmarkStatus()
+    }
+
+    switch placePageData.objectType {
+    case .POI, .trackRecording:
+      break
+    case .bookmark:
+      guard bookmarksManager.hasBookmark(placePageData.bookmarkData!.bookmarkId) else {
+        presenter?.closeAnimated()
+        return
+      }
+      updatePlacePage()
+    case .track:
+      guard bookmarksManager.hasTrack(placePageData.trackData!.trackId) else {
+        presenter?.closeAnimated()
+        return
+      }
+      updatePlacePage()
+    @unknown default:
+      fatalError("Unknown object type")
     }
-    FrameworkHelper.updatePlacePageData()
-    placePageData.updateBookmarkStatus()
   }
 
   private func addToBookmarksManagerObserverList() {
@@ -239,9 +254,19 @@ extension PlacePageInteractor: ActionBarViewControllerDelegate {
       fatalError("More button should've been handled in ActionBarViewContoller")
     case .track:
       guard placePageData.trackData != nil else { return }
-      // TODO: This is temporary solution. Remove the dialog and use the MWMPlacePageManagerHelper.removeTrack
+      // TODO: (KK) This is temporary solution. Remove the dialog and use the MWMPlacePageManagerHelper.removeTrack
       // directly here when the track recovery mechanism will be implemented.
       showTrackDeletionConfirmationDialog()
+    case .saveTrackRecording:
+      // TODO: (KK) pass name typed by user
+      TrackRecordingManager.shared.processAction(.stopAndSave(name: "")) { [weak self] result in
+        switch result {
+        case .success:
+          break
+        case .error:
+          self?.presenter?.closeAnimated()
+        }
+      }
     @unknown default:
       fatalError()
     }
@@ -277,8 +302,8 @@ extension PlacePageInteractor: ElevationProfileViewControllerDelegate {
   }
 
   func updateMapPoint(_ point: CLLocationCoordinate2D, distance: Double) {
-    guard let trackId = placePageData.trackData?.trackId else { return }
-    BookmarksManager.shared().setElevationActivePoint(point, distance: distance, trackId: trackId)
+    guard let trackData = placePageData.trackData, trackData.elevationProfileData?.isTrackRecording == false else { return }
+    BookmarksManager.shared().setElevationActivePoint(point, distance: distance, trackId: trackData.trackId)
   }
 }
 
@@ -301,6 +326,10 @@ extension PlacePageInteractor: PlacePageHeaderViewControllerDelegate {
       shareViewController.present(inParentViewController: mapViewController, anchorView: sourceView)
     case .track:
       presenter?.showShareTrackMenu()
+    case .trackRecording:
+      let currentLocation = LocationManager.lastLocation()?.coordinate ?? placePageData.locationCoordinate
+      let shareMyPositionViewController = ActivityViewController.share(forMyPosition: currentLocation)
+      shareMyPositionViewController.present(inParentViewController: mapViewController, anchorView: sourceView)
     default:
       fatalError()
     }
diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.h b/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.h
index a82a84c698..d8c5f33ab1 100644
--- a/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.h
+++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.h
@@ -3,6 +3,7 @@ typedef NS_ENUM(NSInteger, MWMActionBarButtonType) {
   MWMActionBarButtonTypeBookingSearch,
   MWMActionBarButtonTypeBookmark,
   MWMActionBarButtonTypeTrack,
+  MWMActionBarButtonTypeSaveTrackRecording,
   MWMActionBarButtonTypeCall,
   MWMActionBarButtonTypeDownload,
   MWMActionBarButtonTypeMore,
diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.m b/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.m
index 0a7401bafa..dd38a97b9d 100644
--- a/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.m
+++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.m
@@ -19,6 +19,8 @@ NSString *titleForButton(MWMActionBarButtonType type, BOOL isSelected) {
     case MWMActionBarButtonTypeBookmark:
     case MWMActionBarButtonTypeTrack:
       return L(isSelected ? @"delete" : @"save");
+    case MWMActionBarButtonTypeSaveTrackRecording:
+      return L(@"save");
     case MWMActionBarButtonTypeRouteFrom:
       return L(@"p2p_from_here");
     case MWMActionBarButtonTypeRouteTo:
@@ -55,7 +57,8 @@ NSString *titleForButton(MWMActionBarButtonType type, BOOL isSelected) {
   self.label.text = titleForButton(self.type, isSelected);
   self.extraBackground.hidden = YES;
   self.button.coloring = MWMButtonColoringBlack;
-  
+  [self.button.imageView setContentMode:UIViewContentModeScaleAspectFit];
+
   switch (self.type) {
     case MWMActionBarButtonTypeDownload: {
       if (self.mapDownloadProgress)
@@ -108,6 +111,9 @@ NSString *titleForButton(MWMActionBarButtonType type, BOOL isSelected) {
       [self.button setImage:[[UIImage imageNamed:@"ic_route_manager_trash"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];
       self.button.coloring = MWMButtonColoringRed;
       break;
+    case MWMActionBarButtonTypeSaveTrackRecording:
+      [self.button setImage:[UIImage imageNamed:@"ic_placepage_save_track_recording"] forState:UIControlStateNormal];
+      break;
     case MWMActionBarButtonTypeRouteFrom:
       [self.button setImage:[UIImage imageNamed:@"ic_route_from"] forState:UIControlStateNormal];
       break;
diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackRecordingLayout.swift b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackRecordingLayout.swift
new file mode 100644
index 0000000000..5dba5ce430
--- /dev/null
+++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackRecordingLayout.swift
@@ -0,0 +1,92 @@
+final class PlacePageTrackRecordingLayout: IPlacePageLayout {
+  private var placePageData: PlacePageData
+  private var interactor: PlacePageInteractor
+  private let storyboard: UIStoryboard
+  weak var presenter: PlacePagePresenterProtocol?
+
+  lazy var bodyViewControllers: [UIViewController] = {
+    return configureViewControllers()
+  }()
+
+  var actionBar: ActionBarViewController? {
+    actionBarViewController
+  }
+
+  var navigationBar: UIViewController? {
+    placePageNavigationViewController
+  }
+
+  lazy var headerViewControllers: [UIViewController] = {
+    [headerViewController]
+  }()
+
+  lazy var headerViewController: PlacePageHeaderViewController = {
+    return PlacePageHeaderBuilder.build(data: placePageData, delegate: interactor, headerType: .flexible)
+  }()
+
+  lazy var placePageNavigationViewController: PlacePageHeaderViewController = {
+    return PlacePageHeaderBuilder.build(data: placePageData, delegate: interactor, headerType: .fixed)
+  }()
+
+  lazy var editTrackViewController: PlacePageEditBookmarkOrTrackViewController = {
+    let vc = storyboard.instantiateViewController(ofType: PlacePageEditBookmarkOrTrackViewController.self)
+    vc.view.isHidden = true
+    vc.delegate = interactor
+    return vc
+  }()
+
+  lazy var elevationProfileViewController: ElevationProfileViewController? = {
+    guard let trackData = placePageData.trackData else {
+      return nil
+    }
+    return ElevationProfileBuilder.build(trackInfo: trackData.trackInfo,
+                                         elevationProfileData: trackData.elevationProfileData,
+                                         delegate: interactor)
+  }()
+
+  lazy var actionBarViewController: ActionBarViewController = {
+    let vc = storyboard.instantiateViewController(ofType: ActionBarViewController.self)
+    vc.placePageData = placePageData
+    vc.canAddStop = MWMRouter.canAddIntermediatePoint()
+    vc.isRoutePlanning = MWMNavigationDashboardManager.shared().state != .hidden
+    vc.delegate = interactor
+    return vc
+  }()
+
+  init(interactor: PlacePageInteractor, storyboard: UIStoryboard, data: PlacePageData) {
+    self.interactor = interactor
+    self.storyboard = storyboard
+    self.placePageData = data
+  }
+
+  private func configureViewControllers() -> [UIViewController] {
+    var viewControllers = [UIViewController]()
+
+    if let elevationProfileViewController {
+      viewControllers.append(elevationProfileViewController)
+    }
+
+    placePageData.onTrackRecordingProgressUpdate = { [weak self] in
+      self?.updateTrackRecordingRelatedSections()
+    }
+
+    return viewControllers
+  }
+
+  func calculateSteps(inScrollView scrollView: UIScrollView, compact: Bool) -> [PlacePageState] {
+    var steps: [PlacePageState] = []
+    let scrollHeight = scrollView.height
+    steps.append(.closed(-scrollHeight))
+    steps.append(.full(0))
+    return steps
+  }
+}
+
+private extension PlacePageTrackRecordingLayout {
+  func updateTrackRecordingRelatedSections() {
+    guard let elevationProfileViewController, let trackInfo = placePageData.trackData?.trackInfo else { return }
+    headerViewController.setTitle(placePageData.previewData.title, secondaryTitle: nil)
+    elevationProfileViewController.presenter?.update(trackInfo: trackInfo, profileData: placePageData.trackData?.elevationProfileData)
+    presenter?.layoutIfNeeded()
+  }
+}