Compare commits

...
Sign in to create a new pull request.

4 commits

Author SHA1 Message Date
98723bb6d9 [ios] add track recording button to the tab bar
1. switch layout to the stack view
2. add the track recording (TR) button to the stack
3. subscribe TR button state on the recording updates
4. implement TR button "blinking"

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-11-22 19:06:47 +04:00
65e99a8ad1 [ios] add new icon for the track recoding main tab button
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-11-22 19:06:47 +04:00
b9c5805bf1 [ios] add observation for the track recorder state
To bind state with the button on the main screen

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-11-22 19:06:47 +04:00
970899cb05 [ios] add red style for the main tab bar button
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-11-22 19:06:47 +04:00
10 changed files with 285 additions and 111 deletions

View file

@ -5,7 +5,8 @@ typedef NS_ENUM(NSUInteger, MWMButtonColoring)
MWMButtonColoringBlack,
MWMButtonColoringWhite,
MWMButtonColoringWhiteText,
MWMButtonColoringGray
MWMButtonColoringGray,
MWMButtonColoringRed
};
@interface MWMButton : UIButton

View file

@ -68,6 +68,9 @@ static NSString * const kSelectedPattern = @"%@_selected_%@";
case MWMButtonColoringGray:
self.tintColor = [UIColor blackDividers];
break;
case MWMButtonColoringRed:
self.tintColor = [[UIColor buttonRed] colorWithAlphaComponent:0.5];
break;
case MWMButtonColoringWhiteText:
self.tintColor = [UIColor whitePrimaryTextHighlighted];
break;
@ -100,6 +103,7 @@ static NSString * const kSelectedPattern = @"%@_selected_%@";
case MWMButtonColoringBlue:
case MWMButtonColoringOther:
case MWMButtonColoringGray:
case MWMButtonColoringRed:
break;
}
}
@ -137,9 +141,12 @@ static NSString * const kSelectedPattern = @"%@_selected_%@";
case MWMButtonColoringGray:
self.tintColor = [UIColor blackHintText];
break;
case MWMButtonColoringRed:
self.tintColor = [UIColor buttonRed];
case MWMButtonColoringOther:
self.imageView.image = [self imageForState:UIControlStateNormal];
break;
break;
}
}
@ -157,6 +164,8 @@ static NSString * const kSelectedPattern = @"%@_selected_%@";
self.coloring = MWMButtonColoringOther;
else if ([coloring isEqualToString:@"MWMGray"])
self.coloring = MWMButtonColoringGray;
else if ([coloring isEqualToString:@"MWMRed"])
self.coloring = MWMButtonColoringRed;
else
NSAssert(false, @"Invalid UIButton's coloring!");
}

View file

@ -348,5 +348,10 @@ class GlobalStyleSheet: IStyleSheet {
s.tintColor = colors.white
s.coloring = MWMButtonColoring.white
}
theme.add(styleName: "MWMRed") { (s) -> (Void) in
s.tintColor = colors.buttonRed
s.coloring = MWMButtonColoring.blue
}
}
}

View file

@ -33,7 +33,12 @@ final class TrackRecordingManager: NSObject {
static let shared: TrackRecordingManager = TrackRecordingManager(trackRecorder: FrameworkHelper.self)
private let trackRecorder: TrackRecorder.Type
private(set) var recordingState: TrackRecordingState = .inactive
private var observations: [Observation] = []
private(set) var recordingState: TrackRecordingState = .inactive {
didSet {
notifyObservers()
}
}
private init(trackRecorder: TrackRecorder.Type) {
self.trackRecorder = trackRecorder
@ -50,10 +55,24 @@ final class TrackRecordingManager: NSObject {
}
}
func addObserver(_ observer: AnyObject, recordingStateDidChangeHandler: @escaping TrackRecordingStateHandler) {
let observation = Observation(observer: observer, recordingStateDidChangeHandler: recordingStateDidChangeHandler)
observations.append(observation)
recordingStateDidChangeHandler(recordingState)
}
func removeObserver(_ observer: AnyObject) {
observations.removeAll { $0.observer === observer }
}
private func notifyObservers() {
observations = observations.filter { $0.observer != nil }
observations.forEach { $0.recordingStateDidChangeHandler?(recordingState) }
}
private func handleError(_ error: TrackRecordingError, completion: (CompletionHandler)? = nil) {
switch error {
case .locationIsProhibited:
completion?()
// Show alert to enable location
LocationManager.checkLocationStatus()
}
@ -75,7 +94,7 @@ final class TrackRecordingManager: NSObject {
recordingState = .active
completion?()
case .active:
break
completion?()
case .error(let trackRecordingError):
handleError(trackRecordingError, completion: completion)
}

View file

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "ic_menu_bookmark_track_recordign.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View file

@ -3,20 +3,34 @@ protocol BottomTabBarInteractorProtocol: AnyObject {
func openHelp()
func openFaq()
func openBookmarks()
func openTrackRecorder()
func openMenu()
}
class BottomTabBarInteractor {
weak var presenter: BottomTabBarPresenterProtocol?
private weak var viewController: UIViewController?
private weak var viewController: BottomTabBarViewController?
private weak var mapViewController: MapViewController?
private weak var controlsManager: MWMMapViewControlsManager?
private weak var searchManager = MWMSearchManager.manager()
init(viewController: UIViewController, mapViewController: MapViewController, controlsManager: MWMMapViewControlsManager) {
private let trackRecordingManager = TrackRecordingManager.shared
init(viewController: BottomTabBarViewController, mapViewController: MapViewController, controlsManager: MWMMapViewControlsManager) {
self.viewController = viewController
self.mapViewController = mapViewController
self.controlsManager = controlsManager
self.subscribeOnTrackRecordingState()
}
private func subscribeOnTrackRecordingState() {
trackRecordingManager.addObserver(self) { [weak self] state in
guard let self else { return }
self.viewController?.setTrackRecordingState(state)
}
}
deinit {
trackRecordingManager.removeObserver(self)
}
}
@ -44,7 +58,16 @@ extension BottomTabBarInteractor: BottomTabBarInteractorProtocol {
func openBookmarks() {
mapViewController?.bookmarksCoordinator.open()
}
func openTrackRecorder() {
switch trackRecordingManager.recordingState {
case .inactive, .error:
trackRecordingManager.processAction(.start)
case .active:
trackRecordingManager.processAction(.stop)
}
}
func openMenu() {
guard let state = controlsManager?.menuState else {
fatalError("ERROR: Failed to retrieve the current MapViewControlsManager's state.")

View file

@ -3,6 +3,7 @@ protocol BottomTabBarPresenterProtocol: AnyObject {
func onSearchButtonPressed()
func onHelpButtonPressed(withBadge: Bool)
func onBookmarksButtonPressed()
func onTrackRecordingButtonPressed()
func onMenuButtonPressed()
}
@ -30,6 +31,10 @@ extension BottomTabBarPresenter: BottomTabBarPresenterProtocol {
interactor.openBookmarks()
}
func onTrackRecordingButtonPressed() {
interactor.openTrackRecorder()
}
func onMenuButtonPressed() {
interactor.openMenu()
}

View file

@ -7,6 +7,7 @@ class BottomTabBarViewController: UIViewController {
@IBOutlet var searchButton: MWMButton!
@IBOutlet var helpButton: MWMButton!
@IBOutlet var bookmarksButton: MWMButton!
@IBOutlet var trackRecordingButton: BottomTabBarButton!
@IBOutlet var moreButton: MWMButton!
@IBOutlet var downloadBadge: UIView!
@IBOutlet var helpBadge: UIView!
@ -22,9 +23,18 @@ class BottomTabBarViewController: UIViewController {
updateBadge()
}
}
var isTrackRecordingEnabled: Bool = false {
didSet {
updateTrackRecordingButton()
}
}
private var trackRecordingBlinkTimer: Timer?
var tabBarView: BottomTabBarView {
return view as! BottomTabBarView
}
@objc static var controller: BottomTabBarViewController? {
return MWMMapViewControlsManager.manager()?.tabBarController
}
@ -43,12 +53,17 @@ class BottomTabBarViewController: UIViewController {
helpButton.setImage(nil, for: .normal)
}
updateBadge()
updateTrackRecordingButton()
}
deinit {
MWMSearchManager.remove(self)
}
func setTrackRecordingState(_ state: TrackRecordingState) {
isTrackRecordingEnabled = state == .active
}
static func updateAvailableArea(_ frame: CGRect) {
BottomTabBarViewController.controller?.updateAvailableArea(frame)
}
@ -69,6 +84,10 @@ class BottomTabBarViewController: UIViewController {
@IBAction func onBookmarksButtonPressed(_ sender: Any) {
presenter.onBookmarksButtonPressed()
}
@IBAction func onTrackRecordingButtonPressed(_ sender: Any) {
presenter.onTrackRecordingButtonPressed()
}
@IBAction func onMenuButtonPressed(_ sender: Any) {
presenter.onMenuButtonPressed()
@ -107,6 +126,22 @@ class BottomTabBarViewController: UIViewController {
downloadBadge.isHidden = isApplicationBadgeHidden
helpBadge.isHidden = !needsToShowHelpBadge()
}
private func updateTrackRecordingButton() {
guard viewIfLoaded != nil else { return }
if isTrackRecordingEnabled {
let kBlinkingDuration = 1.0
trackRecordingBlinkTimer = Timer.scheduledTimer(withTimeInterval: kBlinkingDuration, repeats: true) { [weak self] _ in
guard let self = self else { return }
let coloring = self.trackRecordingButton.coloring
self.trackRecordingButton.coloring = coloring == .red ? .black : .red
}
} else {
trackRecordingBlinkTimer?.invalidate()
trackRecordingBlinkTimer = nil
trackRecordingButton.coloring = .black
}
}
}
// MARK: - Help badge

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23091" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23094" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23079"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23084"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -16,137 +16,199 @@
<outlet property="helpButton" destination="dzf-7Z-N6a" id="ORg-VO-5ar"/>
<outlet property="moreButton" destination="svD-yi-GrZ" id="kjk-ZW-nZN"/>
<outlet property="searchButton" destination="No0-ld-JX3" id="m5F-UT-j94"/>
<outlet property="trackRecordingButton" destination="MXy-h4-kqq" id="hFV-r8-R24"/>
<outlet property="view" destination="zuH-WU-hiP" id="eoa-4I-wKs"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="zuH-WU-hiP" customClass="BottomTabBarView" customModule="Organic_Maps" customModuleProvider="target" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="373" height="84"/>
<rect key="frame" x="0.0" y="0.0" width="368" height="102"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vum-s3-PHx" userLabel="MainButtons" customClass="ExtendedBottomTabBarContainerView" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="373" height="48"/>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="AhP-ky-dOP">
<rect key="frame" x="0.0" y="0.0" width="368" height="48"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dzf-7Z-N6a" userLabel="Help" customClass="BottomTabBarButton" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="22.5" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="helpButton"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jAR-wW-hHn" userLabel="Help">
<rect key="frame" x="0.0" y="0.0" width="73.5" height="48"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dzf-7Z-N6a" userLabel="HelpButton" customClass="BottomTabBarButton" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="13" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="helpButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="dzf-7Z-N6a" secondAttribute="height" id="qNJ-0K-sK0"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="30"/>
<inset key="imageEdgeInsets" minX="9" minY="9" maxX="9" maxY="9"/>
<state key="normal" image="logo"/>
<connections>
<action selector="onHelpButtonPressed:" destination="-1" eventType="touchUpInside" id="1gx-P2-sRJ"/>
</connections>
</button>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0pe-0n-d1F" userLabel="HelpBadge">
<rect key="frame" x="45" y="6" width="10" height="10"/>
<color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" notEnabled="YES"/>
</accessibility>
<constraints>
<constraint firstAttribute="height" constant="10" id="FgD-lk-SxR"/>
<constraint firstAttribute="width" constant="10" id="OwT-jV-hqZ"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Badge"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" secondItem="dzf-7Z-N6a" secondAttribute="height" id="qNJ-0K-sK0"/>
<constraint firstItem="dzf-7Z-N6a" firstAttribute="centerY" secondItem="jAR-wW-hHn" secondAttribute="centerY" id="Ap3-FW-MI6"/>
<constraint firstItem="0pe-0n-d1F" firstAttribute="bottom" secondItem="dzf-7Z-N6a" secondAttribute="centerY" constant="-8" id="XvB-KP-XHb"/>
<constraint firstItem="0pe-0n-d1F" firstAttribute="leading" secondItem="dzf-7Z-N6a" secondAttribute="centerX" constant="8" id="ejU-uI-K81"/>
<constraint firstItem="dzf-7Z-N6a" firstAttribute="height" secondItem="jAR-wW-hHn" secondAttribute="height" id="yBH-bv-hjG"/>
<constraint firstItem="dzf-7Z-N6a" firstAttribute="centerX" secondItem="jAR-wW-hHn" secondAttribute="centerX" id="zUC-VJ-huw"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="30"/>
<inset key="imageEdgeInsets" minX="9" minY="9" maxX="9" maxY="9"/>
<state key="normal" image="logo"/>
<connections>
<action selector="onHelpButtonPressed:" destination="-1" eventType="touchUpInside" id="1gx-P2-sRJ"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" horizontalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="No0-ld-JX3" userLabel="Search" customClass="BottomTabBarButton" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="116" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="searchButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="No0-ld-JX3" secondAttribute="height" id="2bW-fc-Hsh"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_menu_search"/>
<connections>
<action selector="onSearchButtonPressed:" destination="-1" eventType="touchUpInside" id="0D5-RB-HBQ"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dgG-ki-3tB" userLabel="Bookmarks" customClass="BottomTabBarButton" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="209" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="bookmarksButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="dgG-ki-3tB" secondAttribute="height" id="o3b-it-lrV"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_menu_bookmark_list"/>
<connections>
<action selector="onBookmarksButtonPressed:" destination="-1" eventType="touchUpInside" id="9Z1-eg-xth"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="svD-yi-GrZ" userLabel="Menu" customClass="BottomTabBarButton" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="302.5" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="menuButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="svD-yi-GrZ" secondAttribute="height" id="gmG-3a-Mqe"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_menu"/>
<connections>
<action selector="onMenuButtonPressed:" destination="-1" eventType="touchUpInside" id="rzb-y4-nR1"/>
</connections>
</button>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uDI-ZC-4wx" userLabel="DownloadBadge">
<rect key="frame" x="329.5" y="11" width="10" height="10"/>
<color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" notEnabled="YES"/>
</accessibility>
<constraints>
<constraint firstAttribute="width" constant="10" id="tEP-Xi-qnU"/>
<constraint firstAttribute="height" constant="10" id="wNg-5Z-7AO"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Badge"/>
</userDefinedRuntimeAttributes>
</view>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0pe-0n-d1F" userLabel="HelpBadge">
<rect key="frame" x="49.5" y="11" width="10" height="10"/>
<color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" notEnabled="YES"/>
</accessibility>
<view contentMode="scaleToFill" id="Bni-Da-2VY" userLabel="Search">
<rect key="frame" x="73.5" y="0.0" width="73.5" height="48"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" horizontalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="No0-ld-JX3" userLabel="SearchButton" customClass="BottomTabBarButton" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="13" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="searchButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="No0-ld-JX3" secondAttribute="height" id="2bW-fc-Hsh"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_menu_search"/>
<connections>
<action selector="onSearchButtonPressed:" destination="-1" eventType="touchUpInside" id="0D5-RB-HBQ"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="R1O-Jm-AuS"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="10" id="FgD-lk-SxR"/>
<constraint firstAttribute="width" constant="10" id="OwT-jV-hqZ"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="centerY" secondItem="Bni-Da-2VY" secondAttribute="centerY" id="1nY-pz-Iwo"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="height" secondItem="Bni-Da-2VY" secondAttribute="height" id="Mki-K8-PuJ"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="centerX" secondItem="Bni-Da-2VY" secondAttribute="centerX" id="s5M-n4-MHR"/>
</constraints>
</view>
<view contentMode="scaleToFill" id="P21-bX-zyL" userLabel="Bookmarks">
<rect key="frame" x="147" y="0.0" width="73.5" height="48"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dgG-ki-3tB" userLabel="BookmarksButton" customClass="BottomTabBarButton" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="13" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="bookmarksButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="dgG-ki-3tB" secondAttribute="height" id="o3b-it-lrV"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_menu_bookmark_list"/>
<connections>
<action selector="onBookmarksButtonPressed:" destination="-1" eventType="touchUpInside" id="9Z1-eg-xth"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="4P9-nh-Bry"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="dgG-ki-3tB" firstAttribute="height" secondItem="P21-bX-zyL" secondAttribute="height" id="Ej4-ay-Ab4"/>
<constraint firstItem="dgG-ki-3tB" firstAttribute="centerY" secondItem="P21-bX-zyL" secondAttribute="centerY" id="Wtc-uP-edm"/>
<constraint firstItem="dgG-ki-3tB" firstAttribute="centerX" secondItem="P21-bX-zyL" secondAttribute="centerX" id="Yqq-l7-p1H"/>
</constraints>
</view>
<view contentMode="scaleToFill" id="Ykj-ky-CGV" userLabel="TrackRecording">
<rect key="frame" x="220.5" y="0.0" width="73.5" height="48"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" horizontalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MXy-h4-kqq" userLabel="TrackRecordingButton" customClass="BottomTabBarButton" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="13" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="searchButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="MXy-h4-kqq" secondAttribute="height" id="lXl-PF-HqV"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_menu_bookmark_track_recordign"/>
<connections>
<action selector="onTrackRecordingButtonPressed:" destination="-1" eventType="touchUpInside" id="fam-CU-Noz"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="ird-h2-fT0"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="MXy-h4-kqq" firstAttribute="centerX" secondItem="Ykj-ky-CGV" secondAttribute="centerX" id="6je-Ab-XpB"/>
<constraint firstItem="MXy-h4-kqq" firstAttribute="centerY" secondItem="Ykj-ky-CGV" secondAttribute="centerY" id="Vsi-iz-jFT"/>
<constraint firstItem="MXy-h4-kqq" firstAttribute="height" secondItem="Ykj-ky-CGV" secondAttribute="height" id="sUO-pB-X6q"/>
</constraints>
</view>
<view contentMode="scaleToFill" id="DjB-sk-2BR" userLabel="Menu">
<rect key="frame" x="294" y="0.0" width="73.5" height="48"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="svD-yi-GrZ" userLabel="MenuButton" customClass="BottomTabBarButton" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="13" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="menuButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="svD-yi-GrZ" secondAttribute="height" id="gmG-3a-Mqe"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_menu"/>
<connections>
<action selector="onMenuButtonPressed:" destination="-1" eventType="touchUpInside" id="rzb-y4-nR1"/>
</connections>
</button>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uDI-ZC-4wx" userLabel="DownloadBadge">
<rect key="frame" x="45" y="6" width="10" height="10"/>
<color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" notEnabled="YES"/>
</accessibility>
<constraints>
<constraint firstAttribute="width" constant="10" id="tEP-Xi-qnU"/>
<constraint firstAttribute="height" constant="10" id="wNg-5Z-7AO"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Badge"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="T45-ID-NKx"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="uDI-ZC-4wx" firstAttribute="leading" secondItem="svD-yi-GrZ" secondAttribute="centerX" constant="8" id="1CO-my-gWL"/>
<constraint firstItem="svD-yi-GrZ" firstAttribute="centerX" secondItem="DjB-sk-2BR" secondAttribute="centerX" id="Ce4-Jv-5PO"/>
<constraint firstItem="uDI-ZC-4wx" firstAttribute="bottom" secondItem="svD-yi-GrZ" secondAttribute="centerY" constant="-8" id="XTm-hT-7nN"/>
<constraint firstItem="svD-yi-GrZ" firstAttribute="centerY" secondItem="DjB-sk-2BR" secondAttribute="centerY" id="mRd-wo-JfT"/>
<constraint firstItem="svD-yi-GrZ" firstAttribute="height" secondItem="DjB-sk-2BR" secondAttribute="height" id="sOW-J0-Jkm"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Badge"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<accessibility key="accessibilityConfiguration" identifier="MainButtons"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="48" id="69A-eu-uLp"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="centerY" secondItem="vum-s3-PHx" secondAttribute="centerY" id="8nL-zT-Y7b"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="height" secondItem="vum-s3-PHx" secondAttribute="height" id="9eR-I7-7at"/>
<constraint firstItem="0pe-0n-d1F" firstAttribute="centerX" secondItem="dzf-7Z-N6a" secondAttribute="centerX" constant="8" id="Ab0-g9-N4P"/>
<constraint firstItem="svD-yi-GrZ" firstAttribute="height" secondItem="vum-s3-PHx" secondAttribute="height" id="Fde-um-JL6"/>
<constraint firstItem="dgG-ki-3tB" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="1.25" id="Jc7-nc-elY"/>
<constraint firstItem="dgG-ki-3tB" firstAttribute="centerY" secondItem="vum-s3-PHx" secondAttribute="centerY" id="JjT-sc-hIY"/>
<constraint firstItem="svD-yi-GrZ" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="1.75" id="Q0b-gd-HwS"/>
<constraint firstItem="dgG-ki-3tB" firstAttribute="height" secondItem="vum-s3-PHx" secondAttribute="height" id="Rs8-Hl-CAc"/>
<constraint firstItem="uDI-ZC-4wx" firstAttribute="centerX" secondItem="svD-yi-GrZ" secondAttribute="centerX" constant="8" id="XNb-Ba-Hn7"/>
<constraint firstItem="dzf-7Z-N6a" firstAttribute="centerY" secondItem="vum-s3-PHx" secondAttribute="centerY" id="Zug-zY-KIX"/>
<constraint firstItem="0pe-0n-d1F" firstAttribute="centerY" secondItem="dzf-7Z-N6a" secondAttribute="centerY" constant="-8" id="pgw-rN-LCe"/>
<constraint firstItem="svD-yi-GrZ" firstAttribute="centerY" secondItem="vum-s3-PHx" secondAttribute="centerY" id="sja-hO-YY3"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="0.75" id="tDb-w1-ueQ"/>
<constraint firstItem="dzf-7Z-N6a" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="0.25" id="u3G-gY-98J"/>
<constraint firstItem="dzf-7Z-N6a" firstAttribute="height" secondItem="vum-s3-PHx" secondAttribute="height" id="yTg-8g-H1p"/>
<constraint firstItem="uDI-ZC-4wx" firstAttribute="centerY" secondItem="svD-yi-GrZ" secondAttribute="centerY" constant="-8" id="yq3-ui-IaL"/>
<constraint firstAttribute="height" constant="48" id="ZIk-Bv-e9c"/>
</constraints>
</view>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="aaw-Hz-zma"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="vum-s3-PHx" firstAttribute="top" secondItem="zuH-WU-hiP" secondAttribute="top" id="PQS-ro-25e"/>
<constraint firstItem="vum-s3-PHx" firstAttribute="leading" secondItem="zuH-WU-hiP" secondAttribute="leading" id="kza-JN-Dul"/>
<constraint firstAttribute="trailing" secondItem="vum-s3-PHx" secondAttribute="trailing" id="sM6-P2-rN9"/>
<constraint firstItem="AhP-ky-dOP" firstAttribute="leading" secondItem="aaw-Hz-zma" secondAttribute="leading" id="57L-bk-NNk"/>
<constraint firstItem="aaw-Hz-zma" firstAttribute="trailing" secondItem="AhP-ky-dOP" secondAttribute="trailing" id="6aB-xy-hDi"/>
<constraint firstAttribute="top" secondItem="AhP-ky-dOP" secondAttribute="top" id="IiX-UC-GXc"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="mainButtonsView" destination="vum-s3-PHx" id="fBi-DA-orA"/>
</connections>
<point key="canvasLocation" x="72" y="254"/>
<point key="canvasLocation" x="-37.681159420289859" y="256.47321428571428"/>
</view>
</objects>
<resources>
<image name="ic_menu" width="48" height="48"/>
<image name="ic_menu_bookmark_list" width="48" height="48"/>
<image name="ic_menu_bookmark_track_recordign" width="432" height="432"/>
<image name="ic_menu_search" width="48" height="48"/>
<image name="logo" width="512" height="512"/>
</resources>