[ios] Redesign and refactor main Search screen UI #10213

Merged
root merged 10 commits from ios/redesing-search-vc into master 2025-02-25 12:17:35 +00:00
55 changed files with 2207 additions and 1955 deletions

View file

@ -1,11 +1,11 @@
#import "MWMBottomMenuState.h"
#import "MWMMapDownloaderMode.h"
#import "MWMNavigationDashboardManager.h"
#import "MWMSearchManager.h"
@class MapViewController;
@class BottomTabBarViewController;
@class TrackRecordingViewController;
@protocol MWMFeatureHolder;
@interface MWMMapViewControlsManager : NSObject
@ -47,7 +47,6 @@
- (void)actionDownloadMaps:(MWMMapDownloaderMode)mode;
- (BOOL)searchText:(NSString *)text forInputLocale:(NSString *)locale;
- (void)searchTextOnMap:(NSString *)text forInputLocale:(NSString *)locale;
- (void)hideSearch;
#pragma mark - MWMFeatureHolder

View file

@ -5,7 +5,6 @@
#import "MWMNetworkPolicy+UI.h"
#import "MWMPlacePageManager.h"
#import "MWMPlacePageProtocol.h"
#import "MWMSearchManager.h"
#import "MWMSideButtons.h"
#import "MWMTrafficButtonViewController.h"
#import "MWMMapWidgetsHelper.h"
@ -27,8 +26,7 @@ namespace {
NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
} // namespace
@interface MWMMapViewControlsManager () <BottomMenuDelegate,
MWMSearchManagerObserver>
@interface MWMMapViewControlsManager () <BottomMenuDelegate>
@property(nonatomic) MWMSideButtons *sideButtons;
@property(nonatomic) MWMTrafficButtonViewController *trafficButton;
@ -36,7 +34,7 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
@property(nonatomic) UIViewController *menuController;
@property(nonatomic) id<MWMPlacePageProtocol> placePageManager;
@property(nonatomic) MWMNavigationDashboardManager *navigationManager;
@property(nonatomic) MWMSearchManager *searchManager;
@property(nonatomic) SearchOnMapManager *searchManager;
@property(weak, nonatomic) MapViewController *ownerController;
@ -50,6 +48,7 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
+ (MWMMapViewControlsManager *)manager {
return [MapViewController sharedController].controlsManager;
}
- (instancetype)initWithParentController:(MapViewController *)controller {
if (!controller)
return nil;
@ -67,6 +66,7 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
[TrackRecordingManager.shared addObserver:self recordingIsActiveDidChangeHandler:^(TrackRecordingState state, TrackInfo * trackInfo) {
[self setTrackRecordingButtonHidden:state == TrackRecordingStateInactive];
}];
self.searchManager = controller.searchManager;
return self;
}
@ -75,7 +75,6 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
}
- (UIStatusBarStyle)preferredStatusBarStyle {
BOOL const isSearchUnderStatusBar = (self.searchManager.state != MWMSearchManagerStateHidden);
BOOL const isNavigationUnderStatusBar = self.navigationManager.state != MWMNavigationDashboardStateHidden &&
self.navigationManager.state != MWMNavigationDashboardStateNavigation;
BOOL const isMenuViewUnderStatusBar = self.menuState == MWMBottomMenuStateActive;
@ -83,7 +82,7 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
BOOL const isAddPlaceUnderStatusBar =
[self.ownerController.view hasSubviewWithViewClass:[MWMAddPlaceNavigationBar class]];
BOOL const isNightMode = [UIColor isNightMode];
BOOL const isSomethingUnderStatusBar = isSearchUnderStatusBar || isNavigationUnderStatusBar ||
BOOL const isSomethingUnderStatusBar = isNavigationUnderStatusBar ||
isDirectionViewUnderStatusBar || isMenuViewUnderStatusBar ||
isAddPlaceUnderStatusBar;
@ -101,7 +100,6 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
[self.trafficButton viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[self.trackRecordingButton viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[self.tabBarController viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[self.searchManager viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
#pragma mark - MWMPlacePageViewManager
@ -110,24 +108,19 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
if (![self searchText:text forInputLocale:locale])
return;
self.searchManager.state = MWMSearchManagerStateMapSearch;
[self.searchManager startSearchingWithIsRouting:NO];
}
- (BOOL)searchText:(NSString *)text forInputLocale:(NSString *)locale {
if (text.length == 0)
return NO;
self.searchManager.state = MWMSearchManagerStateTableSearch;
[self.searchManager searchText:text forInputLocale:locale withCategory:NO];
[self.searchManager startSearchingWithIsRouting:NO];
[self.searchManager searchText:text locale:locale isCategory:NO];
return YES;
}
- (void)hideSearch {
self.searchManager.state = MWMSearchManagerStateHidden;
}
#pragma mark - BottomMenuDelegate
#pragma mark - BottomMenu
- (void)actionDownloadMaps:(MWMMapDownloaderMode)mode {
[self.ownerController openMapsDownloader:mode];
}
@ -146,7 +139,7 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
MapViewController *ownerController = self.ownerController;
self.isAddingPlace = YES;
self.searchManager.state = MWMSearchManagerStateHidden;
[self.searchManager close];
self.menuState = MWMBottomMenuStateHidden;
self.trafficButtonHidden = YES;
@ -181,15 +174,6 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
[[MapsAppDelegate theApp] enableStandby];
}
#pragma mark - MWMSearchManagerObserver
- (void)onSearchManagerStateChanged {
auto state = [MWMSearchManager manager].state;
if (!IPAD && state == MWMSearchManagerStateHidden) {
self.hidden = NO;
}
}
#pragma mark - Routing
- (void)onRoutePrepare {
@ -201,16 +185,12 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
}
- (void)onRouteRebuild {
if (IPAD)
self.searchManager.state = MWMSearchManagerStateHidden;
[self.ownerController.bookmarksCoordinator close];
[self.navigationManager onRoutePlanning];
self.promoButton.hidden = YES;
}
- (void)onRouteReady:(BOOL)hasWarnings {
self.searchManager.state = MWMSearchManagerStateHidden;
[self.navigationManager onRouteReady:hasWarnings];
self.promoButton.hidden = YES;
}
@ -226,7 +206,6 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
}
- (void)onRouteStop {
self.searchManager.state = MWMSearchManagerStateHidden;
self.sideButtons.zoomHidden = self.zoomHidden;
[self.navigationManager onRouteStop];
self.disableStandbyOnRouteFollowing = NO;
@ -236,17 +215,6 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
#pragma mark - Properties
/*
- (UIButton *)promoButton {
if (!_promoButton) {
PromoCoordinator *coordinator = [[PromoCoordinator alloc] initWithViewController:self.ownerController
campaign:_promoDiscoveryCampaign];
_promoButton = [[PromoButton alloc] initWithCoordinator:coordinator];
}
return _promoButton;
}
*/
- (MWMSideButtons *)sideButtons {
if (!_sideButtons)
_sideButtons = [[MWMSideButtons alloc] initWithParentView:self.ownerController.controlsView];
@ -283,14 +251,6 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
return _navigationManager;
}
- (MWMSearchManager *)searchManager {
if (!_searchManager) {
_searchManager = [[MWMSearchManager alloc] init];
[MWMSearchManager addObserver:self];
}
return _searchManager;
}
@synthesize menuState = _menuState;
- (void)setHidden:(BOOL)hidden {

View file

@ -14,5 +14,6 @@
- (void)processMyPositionStateModeEvent:(MWMMyPositionMode)mode;
+ (void)updateAvailableArea:(CGRect)frame;
+ (CGRect)getAvailableArea;
@end

View file

@ -57,6 +57,9 @@ NSString * const kUDDidShowLongTapToShowSideButtonsToast = @"kUDDidShowLongTapTo
}
+ (void)updateAvailableArea:(CGRect)frame { [[self buttons].sideView updateAvailableArea:frame]; }
+ (CGRect)getAvailableArea { return [self buttons].sideView.getAvailableArea; }
- (void)zoomIn
{
GetFramework().Scale(Framework::SCALE_MAG, true);

View file

@ -8,5 +8,6 @@
- (void)setHidden:(BOOL)hidden animated:(BOOL)animated;
- (void)updateAvailableArea:(CGRect)frame;
- (CGRect)getAvailableArea;
@end

View file

@ -144,6 +144,10 @@ CGFloat const kButtonsBottomOffset = 6;
[self setNeedsLayout];
}
- (CGRect)getAvailableArea {
return self.availableArea;
}
- (CGFloat)availableHeight {
return self.availableArea.size.height - kButtonsTopOffset - kButtonsBottomOffset;
}

View file

@ -19,7 +19,7 @@ NSString *const kNavigationControlViewXibName = @"NavigationControlView";
@end
@interface MWMNavigationDashboardManager () <MWMSearchManagerObserver, MWMRoutePreviewDelegate>
@interface MWMNavigationDashboardManager () <SearchOnMapManagerObserver, MWMRoutePreviewDelegate>
@property(copy, nonatomic) NSDictionary *etaAttributes;
@property(copy, nonatomic) NSDictionary *etaSecondaryAttributes;
@ -52,6 +52,10 @@ NSString *const kNavigationControlViewXibName = @"NavigationControlView";
return self;
}
- (SearchOnMapManager *)searchManager {
return [[MapViewController sharedController] searchManager];
}
- (void)loadPreviewWithStatusBoxes {
[NSBundle.mainBundle loadNibNamed:kRoutePreviewIPhoneXibName owner:self options:nil];
auto ownerView = self.ownerView;
@ -238,14 +242,20 @@ NSString *const kNavigationControlViewXibName = @"NavigationControlView";
- (IBAction)stopRoutingButtonAction {
[MWMSearch clear];
[MWMRouter stopRouting];
[self.searchManager close];
}
#pragma mark - MWMSearchManagerObserver
#pragma mark - SearchOnMapManagerObserver
- (void)onSearchManagerStateChanged {
auto state = [MWMSearchManager manager].state;
if (state == MWMSearchManagerStateMapSearch)
[self setMapSearch];
- (void)searchManagerWithDidChangeState:(SearchOnMapState)state {
switch (state) {
case SearchOnMapStateClosed:
[self.navigationInfoView setSearchState:NavigationSearchState::MinimizedNormal animated:YES];
break;
case SearchOnMapStateHidden:
case SearchOnMapStateSearching:
[self setMapSearch];
}
}
#pragma mark - Available area
@ -261,9 +271,9 @@ NSString *const kNavigationControlViewXibName = @"NavigationControlView";
- (void)setState:(MWMNavigationDashboardState)state {
if (state == MWMNavigationDashboardStateHidden)
[MWMSearchManager removeObserver:self];
[self.searchManager removeObserver:self];
else
[MWMSearchManager addObserver:self];
[self.searchManager addObserver:self];
switch (state) {
case MWMNavigationDashboardStateHidden:
[self stateHidden];

View file

@ -25,6 +25,8 @@ typedef NS_ENUM(NSUInteger, MWMNavigationInfoViewState) {
@property(weak, nonatomic) UIView * ownerView;
@property(nonatomic) CGRect availableArea;
- (void)setSearchState:(NavigationSearchState)searchState animated:(BOOL)animated;
- (void)onNavigationInfoUpdated:(MWMNavigationDashboardEntity *)info;
- (void)setMapSearch;

View file

@ -6,7 +6,6 @@
#import "MWMLocationObserver.h"
#import "MWMMapViewControlsCommon.h"
#import "MWMSearch.h"
#import "MWMSearchManager.h"
#import "MapViewController.h"
#import "SwiftBridge.h"
#import "UIImageView+Coloring.h"
@ -143,13 +142,16 @@ BOOL defaultOrientation(CGSize const &size) {
[toastView configWithIsStart:YES withLocationButton:NO];
}
- (SearchOnMapManager *)searchManager {
return [MapViewController sharedController].searchManager;
}
- (IBAction)openSearch {
BOOL const isStart = self.toastView.isStart;
auto searchManager = [MWMSearchManager manager];
searchManager.routingTooltipSearch =
isStart ? MWMSearchManagerRoutingTooltipSearchStart : MWMSearchManagerRoutingTooltipSearchFinish;
searchManager.state = MWMSearchManagerStateDefault;
[self.searchManager setRoutingTooltip:
isStart ? SearchOnMapRoutingTooltipSearchStart : SearchOnMapRoutingTooltipSearchFinish ];
[self.searchManager startSearchingWithIsRouting:YES];
}
- (IBAction)addLocationRoutePoint {
@ -166,12 +168,12 @@ BOOL defaultOrientation(CGSize const &size) {
- (IBAction)searchMainButtonTouchUpInside {
switch (self.searchState) {
case NavigationSearchState::Maximized:
[MWMSearchManager manager].state = MWMSearchManagerStateDefault;
[self.searchManager startSearchingWithIsRouting:YES];
[self setSearchState:NavigationSearchState::MinimizedNormal animated:YES];
break;
case NavigationSearchState::MinimizedNormal:
if (self.state == MWMNavigationInfoViewStatePrepare) {
[MWMSearchManager manager].state = MWMSearchManagerStateDefault;
[self.searchManager startSearchingWithIsRouting:YES];
} else {
[self setSearchState:NavigationSearchState::Maximized animated:YES];
}
@ -183,7 +185,7 @@ BOOL defaultOrientation(CGSize const &size) {
case NavigationSearchState::MinimizedFood:
case NavigationSearchState::MinimizedATM:
[MWMSearch clear];
[MWMSearchManager manager].state = MWMSearchManagerStateHidden;
[self.searchManager hide];
[self setSearchState:NavigationSearchState::MinimizedNormal animated:YES];
break;
}
@ -191,7 +193,6 @@ BOOL defaultOrientation(CGSize const &size) {
- (IBAction)searchButtonTouchUpInside:(MWMButton *)sender {
auto const body = ^(NavigationSearchState state) {
[MWMSearch setSearchOnMap:YES];
NSString *query = [kSearchButtonRequest.at(state) stringByAppendingString:@" "];
NSString *locale = [[AppInfo sharedInfo] languageId];
// Category request from navigation search wheel.

View file

@ -6,6 +6,7 @@
@class EAGLView;
@class MWMMapDownloadDialog;
@class BookmarksCoordinator;
@class SearchOnMapManager;
@protocol MWMLocationModeListener;
@interface MapViewController : MWMViewController
@ -46,11 +47,10 @@
@property(nonatomic, readonly) MWMMapViewControlsManager * _Nonnull controlsManager;
@property(nonatomic, readonly) MWMMapDownloadDialog * _Nonnull downloadDialog;
@property(nonatomic, readonly) BookmarksCoordinator * _Nonnull bookmarksCoordinator;
@property(nonatomic, readonly) SearchOnMapManager * _Nonnull searchManager;
@property(nonatomic) MWMMyPositionMode currentPositionMode;
@property(strong, nonatomic) IBOutlet EAGLView * _Nonnull mapView;
@property(strong, nonatomic) IBOutlet UIView * _Nonnull controlsView;
@property(strong, nonatomic) IBOutlet UIView * _Nonnull searchViewContainer;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint * _Nonnull searchViewContainerLeadingConstraint;
@end

View file

@ -30,6 +30,9 @@
extern NSString *const kMap2FBLoginSegue = @"Map2FBLogin";
extern NSString *const kMap2GoogleLoginSegue = @"Map2GoogleLogin";
static CGFloat kPlacePageCompactWidth = 350;
static CGFloat kPlacePageLeadingOffset = IPAD ? 20 : 0;
typedef NS_ENUM(NSUInteger, UserTouchesAction) { UserTouchesActionNone, UserTouchesActionDrag, UserTouchesActionScale };
namespace {
@ -72,6 +75,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
UIGestureRecognizerDelegate>
@property(nonatomic, readwrite) MWMMapViewControlsManager *controlsManager;
@property(nonatomic, readwrite) SearchOnMapManager *searchManager;
@property(nonatomic) BOOL disableStandbyOnLocationStateMode;
@ -97,7 +101,11 @@ NSString *const kSettingsSegue = @"Map2Settings";
@property(nonatomic) BOOL needDeferFocusNotification;
@property(nonatomic) BOOL deferredFocusValue;
@property(nonatomic) PlacePageViewController *placePageVC;
@property(nonatomic) IBOutlet UIView *placePageContainer;
@property(nonatomic) UIView *placePageContainer;
@property(nonatomic) NSLayoutConstraint *placePageWidthConstraint;
@property(nonatomic) NSLayoutConstraint *placePageLeadingConstraint;
@property(nonatomic) NSLayoutConstraint *placePageTrailingConstraint;
@end
@ -111,6 +119,9 @@ NSString *const kSettingsSegue = @"Map2Settings";
#pragma mark - Map Navigation
- (void)showOrUpdatePlacePage:(PlacePageData *)data {
if (self.searchManager.isSearching)
[self.searchManager setPlaceOnMapSelected:YES];
self.controlsManager.trafficButtonHidden = YES;
if (self.placePageVC != nil) {
[PlacePageBuilder update:(PlacePageViewController *)self.placePageVC with:data];
@ -120,19 +131,58 @@ NSString *const kSettingsSegue = @"Map2Settings";
}
- (void)showPlacePageFor:(PlacePageData *)data {
self.placePageVC = [PlacePageBuilder buildFor:data];
self.placePageContainer.hidden = NO;
self.placePageVC.view.translatesAutoresizingMaskIntoConstraints = NO;
self.placePageVC = [PlacePageBuilder buildFor:data];
[self.placePageContainer addSubview:self.placePageVC.view];
[self.view bringSubviewToFront:self.placePageContainer];
[NSLayoutConstraint activateConstraints:@[
[self.placePageVC.view.topAnchor constraintEqualToAnchor:self.placePageContainer.safeAreaLayoutGuide.topAnchor],
[self.placePageVC.view.leftAnchor constraintEqualToAnchor:self.placePageContainer.leftAnchor],
[self.placePageVC.view.bottomAnchor constraintEqualToAnchor:self.placePageContainer.bottomAnchor],
[self.placePageVC.view.rightAnchor constraintEqualToAnchor:self.placePageContainer.rightAnchor]
]];
[self addChildViewController:self.placePageVC];
[self.placePageVC didMoveToParentViewController:self];
self.placePageVC.view.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[self.placePageVC.view.topAnchor constraintEqualToAnchor:self.placePageContainer.topAnchor],
[self.placePageVC.view.leadingAnchor constraintEqualToAnchor:self.placePageContainer.leadingAnchor],
[self.placePageVC.view.bottomAnchor constraintEqualToAnchor:self.placePageContainer.bottomAnchor],
[self.placePageVC.view.trailingAnchor constraintEqualToAnchor:self.placePageContainer.trailingAnchor]
]];
[self updatePlacePageContainerConstraints];
}
- (void)setupPlacePageContainer {
self.placePageContainer = [[TouchTransparentView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.placePageContainer];
[self.view bringSubviewToFront:self.placePageContainer];
self.placePageContainer.translatesAutoresizingMaskIntoConstraints = NO;
self.placePageLeadingConstraint = [self.placePageContainer.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:kPlacePageLeadingOffset];
self.placePageLeadingConstraint.active = YES;
self.placePageWidthConstraint = [self.placePageContainer.widthAnchor constraintEqualToConstant:0];
self.placePageTrailingConstraint = [self.placePageContainer.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor];
[self.placePageContainer.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor].active = YES;
if (IPAD) {
self.placePageLeadingConstraint.priority = UILayoutPriorityDefaultLow;
[self.placePageContainer.bottomAnchor constraintLessThanOrEqualToAnchor:self.view.bottomAnchor].active = YES;
}
else {
[self.placePageContainer.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
}
[self updatePlacePageContainerConstraints];
}
- (void)updatePlacePageContainerConstraints {
const BOOL isLimitedWidth = IPAD || self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact;
[self.placePageWidthConstraint setConstant:kPlacePageCompactWidth];
if (IPAD && self.searchViewContainer != nil) {
NSLayoutConstraint * leadingToSearchConstraint = [self.placePageContainer.leadingAnchor constraintEqualToAnchor:self.searchViewContainer.trailingAnchor constant:kPlacePageLeadingOffset];
leadingToSearchConstraint.priority = UILayoutPriorityDefaultHigh;
leadingToSearchConstraint.active = isLimitedWidth;
}
[self.placePageWidthConstraint setActive:isLimitedWidth];
[self.placePageTrailingConstraint setActive:!isLimitedWidth];
[self.view layoutIfNeeded];
}
- (void)dismissPlacePage {
@ -160,16 +210,10 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (void)onMapObjectDeselected {
[self hidePlacePage];
MWMSearchManager * searchManager = MWMSearchManager.manager;
BOOL const isSearchResult = searchManager.state == MWMSearchManagerStateResult;
BOOL const isSearching = self.searchManager.isSearching;
BOOL const isNavigationDashboardHidden = [MWMNavigationDashboardManager sharedManager].state == MWMNavigationDashboardStateHidden;
if (isSearchResult) {
if (isNavigationDashboardHidden) {
searchManager.state = MWMSearchManagerStateMapSearch;
} else {
searchManager.state = MWMSearchManagerStateHidden;
}
}
if (isSearching)
[self.searchManager setPlaceOnMapSelected:!isNavigationDashboardHidden];
// Always show the controls during the navigation or planning mode.
if (!isNavigationDashboardHidden)
self.controlsManager.hidden = NO;
@ -177,8 +221,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (void)onSwitchFullScreen {
BOOL const isNavigationDashboardHidden = MWMNavigationDashboardManager.sharedManager.state == MWMNavigationDashboardStateHidden;
BOOL const isSearchHidden = MWMSearchManager.manager.state == MWMSearchManagerStateHidden;
if (isSearchHidden && isNavigationDashboardHidden) {
if (!self.searchManager.isSearching && isNavigationDashboardHidden) {
if (!self.controlsManager.hidden)
[self dismissPlacePage];
self.controlsManager.hidden = !self.controlsManager.hidden;
@ -215,6 +258,10 @@ NSString *const kSettingsSegue = @"Map2Settings";
if ([MWMCarPlayService shared].isCarplayActivated) {
return;
}
if (self.searchManager.isSearching && type == df::TouchEvent::TOUCH_MOVE)
[self.searchManager setMapIsDragging];
NSArray *allTouches = [[event allTouches] allObjects];
if ([allTouches count] < 1)
return;
@ -287,6 +334,12 @@ NSString *const kSettingsSegue = @"Map2Settings";
[self.controlsManager viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if (self.traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass)
[self updatePlacePageContainerConstraints];
}
- (void)didReceiveMemoryWarning {
GetFramework().MemoryWarning();
[super didReceiveMemoryWarning];
@ -305,8 +358,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if ([MWMNavigationDashboardManager sharedManager].state == MWMNavigationDashboardStateHidden &&
[MWMSearchManager manager].state == MWMSearchManagerStateHidden)
if ([MWMNavigationDashboardManager sharedManager].state == MWMNavigationDashboardStateHidden)
self.controlsManager.menuState = self.controlsManager.menuRestoreState;
[self updateStatusBarStyle];
@ -319,6 +371,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (void)viewDidLoad {
[super viewDidLoad];
[self setupPlacePageContainer];
if (@available(iOS 14.0, *))
[self setupTrackPadGestureRecognizers];
@ -441,8 +494,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ([MWMNavigationDashboardManager sharedManager].state == MWMNavigationDashboardStateHidden &&
[MWMSearchManager manager].state == MWMSearchManagerStateHidden)
if ([MWMNavigationDashboardManager sharedManager].state == MWMNavigationDashboardStateHidden)
self.controlsManager.menuRestoreState = self.controlsManager.menuState;
GetFramework().SetRenderingDisabled(false);
}
@ -605,15 +657,13 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (void)performAction:(NSString *)action {
[self.navigationController popToRootViewControllerAnimated:NO];
if (self.isViewLoaded) {
auto searchState = MWMSearchManagerStateHidden;
[MWMRouter stopRouting];
if ([action isEqualToString:@"app.organicmaps.3daction.bookmarks"])
[self.bookmarksCoordinator open];
else if ([action isEqualToString:@"app.organicmaps.3daction.search"])
searchState = MWMSearchManagerStateDefault;
[self.searchManager startSearchingWithIsRouting:NO];
else if ([action isEqualToString:@"app.organicmaps.3daction.route"])
[self.controlsManager onRoutePrepare];
[MWMSearchManager manager].state = searchState;
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self performAction:action];
@ -674,9 +724,20 @@ NSString *const kSettingsSegue = @"Map2Settings";
return _controlsManager;
}
- (SearchOnMapManager *)searchManager {
if (!_searchManager)
_searchManager = [[SearchOnMapManager alloc] initWithNavigationController:self.navigationController];
return _searchManager;
}
- (UIView * _Nullable)searchViewContainer {
return self.searchManager.viewController.view;
}
- (BOOL)hasNavigationBar {
return NO;
}
- (MWMMapDownloadDialog *)downloadDialog {
if (!_downloadDialog)
_downloadDialog = [MWMMapDownloadDialog dialogForController:self];

View file

@ -1,19 +1,19 @@
import Foundation
private func IPAD() -> Bool { return UI_USER_INTERFACE_IDIOM() == .pad }
var isIPad: Bool { return UI_USER_INTERFACE_IDIOM() == .pad }
func L(_ key: String) -> String { return NSLocalizedString(key, comment: "") }
func alternative<T>(iPhone: T, iPad: T) -> T { return IPAD() ? iPad : iPhone }
func alternative<T>(iPhone: T, iPad: T) -> T { return isIPad ? iPad : iPhone }
func iPadSpecific(_ f: () -> Void) {
if IPAD() {
if isIPad {
f()
}
}
func iPhoneSpecific(_ f: () -> Void) {
if !IPAD() {
if !isIPad {
f()
}
}

View file

@ -5,9 +5,7 @@ NS_ASSUME_NONNULL_BEGIN
@class SearchResult;
NS_SWIFT_NAME(Search)
@interface MWMSearch : NSObject
@protocol SearchManager
+ (void)addObserver:(id<MWMSearchObserver>)observer;
+ (void)removeObserver:(id<MWMSearchObserver>)observer;
@ -15,15 +13,21 @@ NS_SWIFT_NAME(Search)
+ (void)searchQuery:(NSString *)query forInputLocale:(NSString *)inputLocale withCategory:(BOOL)isCategory;
+ (void)showResultAtIndex:(NSUInteger)index;
+ (void)showEverywhereSearchResultsOnMap;
+ (void)showViewportSearchResultsOnMap;
+ (NSArray<SearchResult *> *)getResults;
+ (void)clear;
@end
NS_SWIFT_NAME(Search)
@interface MWMSearch : NSObject<SearchManager>
+ (SearchItemType)resultTypeWithRow:(NSUInteger)row;
+ (NSUInteger)containerIndexWithRow:(NSUInteger)row;
+ (SearchResult *)resultWithContainerIndex:(NSUInteger)index;
+ (void)clear;
+ (void)setSearchOnMap:(BOOL)searchOnMap;
+ (NSUInteger)suggestionsCount;
+ (NSUInteger)resultsCount;

View file

@ -17,7 +17,6 @@ using Observers = NSHashTable<Observer>;
@interface MWMSearch () <MWMFrameworkDrapeObserver>
@property(nonatomic) NSUInteger suggestionsCount;
@property(nonatomic) BOOL searchOnMap;
@property(nonatomic) BOOL textChanged;
@ -111,16 +110,8 @@ using Observers = NSHashTable<Observer>;
[self reset];
if (m_query.empty())
return;
if (IPAD) {
[self searchInViewport];
[self searchEverywhere];
} else {
if (self.searchOnMap)
[self searchInViewport];
else
[self searchEverywhere];
}
[self searchInViewport];
[self searchEverywhere];
}
#pragma mark - Add/Remove Observers
@ -165,7 +156,7 @@ using Observers = NSHashTable<Observer>;
+ (void)showResultAtIndex:(NSUInteger)index {
auto const & result = [MWMSearch manager]->m_everywhereResults[index];
GetFramework().ShowSearchResult(result);
GetFramework().SelectSearchResult(result, true);
}
+ (SearchResult *)resultWithContainerIndex:(NSUInteger)index {
@ -175,6 +166,27 @@ using Observers = NSHashTable<Observer>;
return result;
}
+ (void)showEverywhereSearchResultsOnMap {
MWMSearch * manager = [MWMSearch manager];
if (![MWMRouter isRoutingActive])
GetFramework().ShowSearchResults(manager->m_everywhereResults);
}
+ (void)showViewportSearchResultsOnMap {
MWMSearch * manager = [MWMSearch manager];
if (![MWMRouter isRoutingActive])
[manager processViewportChangedEvent];
}
+ (NSArray<SearchResult *> *)getResults {
NSMutableArray<SearchResult *> * results = [[NSMutableArray alloc] initWithCapacity:MWMSearch.resultsCount];
for (NSUInteger i = 0; i < MWMSearch.resultsCount; ++i) {
SearchResult * result = [MWMSearch resultWithContainerIndex:i];
[results addObject:result];
}
return [results copy];
}
+ (SearchItemType)resultTypeWithRow:(NSUInteger)row {
auto itemsIndex = [MWMSearch manager].itemsIndex;
return [itemsIndex resultTypeWithRow:row];
@ -202,18 +214,6 @@ using Observers = NSHashTable<Observer>;
[manager reset];
}
+ (void)setSearchOnMap:(BOOL)searchOnMap {
if (IPAD)
return;
MWMSearch *manager = [MWMSearch manager];
if (manager.searchOnMap == searchOnMap)
return;
manager.searchOnMap = searchOnMap;
if (searchOnMap && ![MWMRouter isRoutingActive])
GetFramework().ShowSearchResults(manager->m_everywhereResults);
[manager update];
}
+ (NSUInteger)suggestionsCount {
return [MWMSearch manager].suggestionsCount;
}
@ -259,8 +259,7 @@ using Observers = NSHashTable<Observer>;
- (void)processViewportChangedEvent {
if (!GetFramework().GetSearchAPI().IsViewportSearchActive())
return;
if (IPAD)
[self searchEverywhere];
[self searchEverywhere];
}
#pragma mark - Properties

View file

@ -1,4 +1,5 @@
enum SearchStyleSheet: String, CaseIterable {
case searchHeader
case searchInstallButton = "SearchInstallButton"
case searchBanner = "SearchBanner"
case searchClosedBackground = "SearchClosedBackground"
@ -16,6 +17,17 @@ enum SearchStyleSheet: String, CaseIterable {
extension SearchStyleSheet: IStyleSheet {
func styleResolverFor(colors: IColors, fonts: IFonts) -> Theme.StyleResolver {
switch self {
case .searchHeader:
return .add { s in
s.backgroundColor = colors.primary
iPhoneSpecific {
s.shadowColor = UIColor.black
s.shadowOffset = CGSize(width: 0, height: 1)
s.shadowOpacity = 0.5
s.shadowRadius = 3
s.cornerRadius = 10
}
}
case .searchInstallButton:
return .add { s in
s.cornerRadius = 10

View file

@ -86,7 +86,6 @@
3490D2E31CE9DD2500D0B838 /* MWMSideButtonsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3490D2DD1CE9DD2500D0B838 /* MWMSideButtonsView.xib */; };
349A13831DEC138C00C7DB60 /* MWMMobileInternetAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = 349A13801DEC138C00C7DB60 /* MWMMobileInternetAlert.m */; };
349A13851DEC138C00C7DB60 /* MWMMobileInternetAlert.xib in Resources */ = {isa = PBXBuildFile; fileRef = 349A13811DEC138C00C7DB60 /* MWMMobileInternetAlert.xib */; };
349D1ABC1E2D05EF004A2006 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349D1ABA1E2D05EF004A2006 /* SearchBar.swift */; };
349D1AD51E2E325B004A2006 /* BottomMenuItemCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 349D1AC61E2E325B004A2006 /* BottomMenuItemCell.xib */; };
349D1AE11E2E325C004A2006 /* BottomTabBarViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 349D1ACD1E2E325B004A2006 /* BottomTabBarViewController.xib */; };
349D1CE41E3F836900A878FD /* UIViewController+Hierarchy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349D1CE21E3F836900A878FD /* UIViewController+Hierarchy.swift */; };
@ -334,7 +333,6 @@
99012852244732DB00C72B10 /* BottomTabBarBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9901284C244732DB00C72B10 /* BottomTabBarBuilder.swift */; };
99012853244732DB00C72B10 /* BottomTabBarInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9901284D244732DB00C72B10 /* BottomTabBarInteractor.swift */; };
990128562449A82500C72B10 /* BottomTabBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990128552449A82400C72B10 /* BottomTabBarView.swift */; };
990F33B624BC915200D0F426 /* SearchActionBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990F33B524BC915200D0F426 /* SearchActionBarView.swift */; };
9917D17F2397B1D600A7E06E /* IPadModalPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9917D17E2397B1D600A7E06E /* IPadModalPresentationController.swift */; };
991FCA2423B11E61009AD684 /* BookmarksStyleSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 991FCA2323B11E61009AD684 /* BookmarksStyleSheet.swift */; };
993DF0B523F6B2EF00AC231A /* PlacePageTrackLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 993DF0B423F6B2EF00AC231A /* PlacePageTrackLayout.swift */; };
@ -396,7 +394,6 @@
995F1613244F0AA50060631D /* BottomMenuLayersCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995F1611244F0AA40060631D /* BottomMenuLayersCell.swift */; };
995F1614244F0AA50060631D /* BottomMenuLayersCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 995F1612244F0AA40060631D /* BottomMenuLayersCell.xib */; };
996D108A24E3DBF2002DD0E2 /* BookmarksCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996D108924E3DBF2002DD0E2 /* BookmarksCoordinator.swift */; };
9977E69C247BFB510073780C /* SearchTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9977E69B247BFB510073780C /* SearchTextField.swift */; };
9977E6A12480E1EE0073780C /* BottomMenuLayerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9977E6A02480E1EE0073780C /* BottomMenuLayerButton.swift */; };
9977E6A32480F9BF0073780C /* BottomMenuLayerButtonRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9977E6A22480F9BF0073780C /* BottomMenuLayerButtonRenderer.swift */; };
998927302449DE1500260CE2 /* TabBarArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9989272F2449DE1500260CE2 /* TabBarArea.swift */; };
@ -493,8 +490,21 @@
ED4DC7772CAEDECC0029B338 /* ProductsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4DC7752CAEDECC0029B338 /* ProductsViewModel.swift */; };
ED4DC7782CAEDECC0029B338 /* ProductButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4DC7732CAEDECC0029B338 /* ProductButton.swift */; };
ED4DC7792CAEDECC0029B338 /* ProductsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4DC7742CAEDECC0029B338 /* ProductsViewController.swift */; };
ED5BAF4B2D688F5B0088D7B1 /* SearchOnMapHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5BAF4A2D688F5A0088D7B1 /* SearchOnMapHeaderView.swift */; };
ED63CEB92BDF8F9D006155C4 /* SettingsTableViewiCloudSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED63CEB62BDF8F9C006155C4 /* SettingsTableViewiCloudSwitchCell.swift */; };
ED70D55C2D5396F300738C1E /* SearchResult.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED70D55A2D5396F300738C1E /* SearchResult.mm */; };
ED70D5892D539A2500738C1E /* SearchOnMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5872D539A2500738C1E /* SearchOnMapViewController.swift */; };
ED70D58A2D539A2500738C1E /* SearchOnMapModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5852D539A2500738C1E /* SearchOnMapModels.swift */; };
ED70D58B2D539A2500738C1E /* SearchOnMapModalTransitionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57E2D539A2500738C1E /* SearchOnMapModalTransitionManager.swift */; };
ED70D58C2D539A2500738C1E /* SearchOnMapPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5862D539A2500738C1E /* SearchOnMapPresenter.swift */; };
ED70D58D2D539A2500738C1E /* ModalScreenPresentationStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57C2D539A2500738C1E /* ModalScreenPresentationStep.swift */; };
ED70D58E2D539A2500738C1E /* SideMenuPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5802D539A2500738C1E /* SideMenuPresentationAnimator.swift */; };
ED70D58F2D539A2500738C1E /* SearchOnMapInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5832D539A2500738C1E /* SearchOnMapInteractor.swift */; };
ED70D5902D539A2500738C1E /* SearchOnMapModalPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57D2D539A2500738C1E /* SearchOnMapModalPresentationController.swift */; };
ED70D5912D539A2500738C1E /* MapPassthroughView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57B2D539A2500738C1E /* MapPassthroughView.swift */; };
ED70D5922D539A2500738C1E /* PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5822D539A2500738C1E /* PlaceholderView.swift */; };
ED70D5932D539A2500738C1E /* SearchOnMapManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5842D539A2500738C1E /* SearchOnMapManager.swift */; };
ED70D5942D539A2500738C1E /* SideMenuDismissalAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57F2D539A2500738C1E /* SideMenuDismissalAnimator.swift */; };
ED77556E2C2C490B0051E656 /* UIAlertController+openInAppActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED77556D2C2C490B0051E656 /* UIAlertController+openInAppActionSheet.swift */; };
ED79A5AB2BD7AA9C00952D1F /* LoadingOverlayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED79A5AA2BD7AA9C00952D1F /* LoadingOverlayViewController.swift */; };
ED79A5AD2BD7BA0F00952D1F /* UIApplication+LoadingOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED79A5AC2BD7BA0F00952D1F /* UIApplication+LoadingOverlay.swift */; };
@ -506,6 +516,7 @@
ED79A5D82BDF8D6100952D1F /* LocalDirectoryMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED79A5D02BDF8D6100952D1F /* LocalDirectoryMonitor.swift */; };
ED7CCC4F2C1362E300E2A737 /* FileType.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED7CCC4E2C1362E300E2A737 /* FileType.swift */; };
ED808D0F2C38407800D52585 /* CircleImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED808D0E2C38407800D52585 /* CircleImageButton.swift */; };
ED810EC52D566E9B00ECDE2C /* SearchOnMapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED810EC42D566E9B00ECDE2C /* SearchOnMapTests.swift */; };
ED8270F02C2071A3005966DA /* SettingsTableViewDetailedSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8270EF2C2071A3005966DA /* SettingsTableViewDetailedSwitchCell.swift */; };
ED83880F2D54DEB3002A0536 /* UIImage+FilledWithColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED83880E2D54DEA4002A0536 /* UIImage+FilledWithColor.swift */; };
ED914AB22D35063A00973C45 /* TextColorStyleSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED914AB12D35063A00973C45 /* TextColorStyleSheet.swift */; };
@ -523,7 +534,6 @@
EDC4E3612C5E2576009286A2 /* RecentlyDeletedCategoriesViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDC4E3412C5D1BD3009286A2 /* RecentlyDeletedCategoriesViewModelTests.swift */; };
EDC4E3692C5E6F5B009286A2 /* MockRecentlyDeletedCategoriesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDC4E3402C5D1BD3009286A2 /* MockRecentlyDeletedCategoriesManager.swift */; };
EDCA7CDF2D317DF9003366CE /* StyleSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDCA7CDE2D317DF9003366CE /* StyleSheet.swift */; };
EDDE060E2D6CAEAF000C328A /* SearchHistoryViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = EDDE060D2D6CAEAF000C328A /* SearchHistoryViewController.xib */; };
EDE243DD2B6D2E640057369B /* AboutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE243D52B6CF3980057369B /* AboutController.swift */; };
EDE243E52B6D3F400057369B /* OSMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE243E42B6D3F400057369B /* OSMView.swift */; };
EDE243E72B6D55610057369B /* InfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE243E02B6D3EA00057369B /* InfoView.swift */; };
@ -617,20 +627,13 @@
F6E2FE7F1E097BA00083EBEC /* MWMPlacePageOpeningHoursCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F6E2FCBD1E097B9F0083EBEC /* MWMPlacePageOpeningHoursCell.xib */; };
F6E2FE821E097BA00083EBEC /* MWMPlacePageOpeningHoursDayView.m in Sources */ = {isa = PBXBuildFile; fileRef = F6E2FCBF1E097B9F0083EBEC /* MWMPlacePageOpeningHoursDayView.m */; };
F6E2FE851E097BA00083EBEC /* MWMPlacePageOpeningHoursWeekDayView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F6E2FCC01E097B9F0083EBEC /* MWMPlacePageOpeningHoursWeekDayView.xib */; };
F6E2FED91E097BA00083EBEC /* MWMSearchContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = F6E2FCF51E097B9F0083EBEC /* MWMSearchContentView.m */; };
F6E2FEDF1E097BA00083EBEC /* MWMSearchManager+Layout.m in Sources */ = {isa = PBXBuildFile; fileRef = F6E2FCF91E097B9F0083EBEC /* MWMSearchManager+Layout.m */; };
F6E2FEE21E097BA00083EBEC /* MWMSearchManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = F6E2FCFB1E097B9F0083EBEC /* MWMSearchManager.mm */; };
F6E2FEE51E097BA00083EBEC /* MWMSearchNoResults.m in Sources */ = {isa = PBXBuildFile; fileRef = F6E2FCFD1E097B9F0083EBEC /* MWMSearchNoResults.m */; };
F6E2FEE81E097BA00083EBEC /* MWMSearchNoResults.xib in Resources */ = {isa = PBXBuildFile; fileRef = F6E2FCFE1E097B9F0083EBEC /* MWMSearchNoResults.xib */; };
F6E2FEEE1E097BA00083EBEC /* MWMSearchView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F6E2FD011E097B9F0083EBEC /* MWMSearchView.xib */; };
F6E2FF2D1E097BA00083EBEC /* MWMSearchCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = F6E2FD2A1E097BA00083EBEC /* MWMSearchCell.mm */; };
F6E2FF301E097BA00083EBEC /* MWMSearchCommonCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = F6E2FD2C1E097BA00083EBEC /* MWMSearchCommonCell.mm */; };
F6E2FF331E097BA00083EBEC /* MWMSearchCommonCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F6E2FD2D1E097BA00083EBEC /* MWMSearchCommonCell.xib */; };
F6E2FF361E097BA00083EBEC /* MWMSearchSuggestionCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = F6E2FD2F1E097BA00083EBEC /* MWMSearchSuggestionCell.mm */; };
F6E2FF391E097BA00083EBEC /* MWMSearchSuggestionCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F6E2FD301E097BA00083EBEC /* MWMSearchSuggestionCell.xib */; };
F6E2FF3C1E097BA00083EBEC /* MWMSearchTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = F6E2FD321E097BA00083EBEC /* MWMSearchTableView.m */; };
F6E2FF3F1E097BA00083EBEC /* MWMSearchTableViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = F6E2FD341E097BA00083EBEC /* MWMSearchTableViewController.mm */; };
F6E2FF421E097BA00083EBEC /* MWMSearchTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F6E2FD351E097BA00083EBEC /* MWMSearchTableViewController.xib */; };
F6E2FF451E097BA00083EBEC /* SettingsTableViewLinkCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E2FD381E097BA00083EBEC /* SettingsTableViewLinkCell.swift */; };
F6E2FF481E097BA00083EBEC /* SettingsTableViewSelectableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E2FD391E097BA00083EBEC /* SettingsTableViewSelectableCell.swift */; };
F6E2FF4B1E097BA00083EBEC /* SettingsTableViewSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E2FD3A1E097BA00083EBEC /* SettingsTableViewSwitchCell.swift */; };
@ -909,7 +912,6 @@
34763EE91F2F394D00F4D2D3 /* MWMTextToSpeech+CPP.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MWMTextToSpeech+CPP.h"; sourceTree = "<group>"; };
34763EEA1F2F3AD700F4D2D3 /* MWMTextToSpeechObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMTextToSpeechObserver.h; sourceTree = "<group>"; };
34763F0B1F30CCAC00F4D2D3 /* MWMEditorCellType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMEditorCellType.h; sourceTree = "<group>"; };
347AD8081F28B4E6007ACB68 /* MWMSearchManagerObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMSearchManagerObserver.h; sourceTree = "<group>"; };
34845DAD1E1649F6003D55B9 /* DownloaderNoResultsEmbedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloaderNoResultsEmbedViewController.swift; sourceTree = "<group>"; };
34845DB11E165E24003D55B9 /* SearchNoResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchNoResultsViewController.swift; sourceTree = "<group>"; };
34845DB51E166084003D55B9 /* Common.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = "<group>"; };
@ -932,7 +934,6 @@
349A35771B53D4C9009677EE /* MWMCircularProgress.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MWMCircularProgress.xib; sourceTree = "<group>"; };
349A35781B53D4C9009677EE /* MWMCircularProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMCircularProgressView.h; sourceTree = "<group>"; };
349A35791B53D4C9009677EE /* MWMCircularProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MWMCircularProgressView.m; sourceTree = "<group>"; };
349D1ABA1E2D05EF004A2006 /* SearchBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = "<group>"; };
349D1AC61E2E325B004A2006 /* BottomMenuItemCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BottomMenuItemCell.xib; sourceTree = "<group>"; };
349D1ACD1E2E325B004A2006 /* BottomTabBarViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BottomTabBarViewController.xib; sourceTree = "<group>"; };
349D1CE21E3F836900A878FD /* UIViewController+Hierarchy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Hierarchy.swift"; sourceTree = "<group>"; };
@ -994,7 +995,6 @@
34B127E61FBDD358008713D9 /* MWMRouterTransitType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMRouterTransitType.h; sourceTree = "<group>"; };
34B127E71FBDD410008713D9 /* MWMRouterTransitStepInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMRouterTransitStepInfo.h; sourceTree = "<group>"; };
34B127E81FBDD410008713D9 /* MWMRouterTransitStepInfo.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMRouterTransitStepInfo.mm; sourceTree = "<group>"; };
34B3806B1F1E46E20087D65B /* MWMSearchManagerState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMSearchManagerState.h; sourceTree = "<group>"; };
34B846A02029DCC10081ECCD /* BMCCategoriesHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BMCCategoriesHeader.swift; sourceTree = "<group>"; };
34B846A72029E8110081ECCD /* BMCDefaultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BMCDefaultViewModel.swift; sourceTree = "<group>"; };
34B924401DC8A29C0008D971 /* MWMMailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMMailViewController.h; sourceTree = "<group>"; };
@ -1252,7 +1252,6 @@
9901284C244732DB00C72B10 /* BottomTabBarBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomTabBarBuilder.swift; sourceTree = "<group>"; };
9901284D244732DB00C72B10 /* BottomTabBarInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomTabBarInteractor.swift; sourceTree = "<group>"; };
990128552449A82400C72B10 /* BottomTabBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomTabBarView.swift; sourceTree = "<group>"; };
990F33B524BC915200D0F426 /* SearchActionBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchActionBarView.swift; sourceTree = "<group>"; };
9917D17E2397B1D600A7E06E /* IPadModalPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPadModalPresentationController.swift; sourceTree = "<group>"; };
991FCA2323B11E61009AD684 /* BookmarksStyleSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksStyleSheet.swift; sourceTree = "<group>"; };
993DF0B423F6B2EF00AC231A /* PlacePageTrackLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlacePageTrackLayout.swift; sourceTree = "<group>"; };
@ -1318,7 +1317,6 @@
995F1611244F0AA40060631D /* BottomMenuLayersCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomMenuLayersCell.swift; sourceTree = "<group>"; };
995F1612244F0AA40060631D /* BottomMenuLayersCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BottomMenuLayersCell.xib; sourceTree = "<group>"; };
996D108924E3DBF2002DD0E2 /* BookmarksCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksCoordinator.swift; sourceTree = "<group>"; };
9977E69B247BFB510073780C /* SearchTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTextField.swift; sourceTree = "<group>"; };
9977E6A02480E1EE0073780C /* BottomMenuLayerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomMenuLayerButton.swift; sourceTree = "<group>"; };
9977E6A22480F9BF0073780C /* BottomMenuLayerButtonRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomMenuLayerButtonRenderer.swift; sourceTree = "<group>"; };
9989272F2449DE1500260CE2 /* TabBarArea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarArea.swift; sourceTree = "<group>"; };
@ -1455,11 +1453,24 @@
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>"; };
ED5BAF4A2D688F5A0088D7B1 /* SearchOnMapHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapHeaderView.swift; sourceTree = "<group>"; };
ED63CEB62BDF8F9C006155C4 /* SettingsTableViewiCloudSwitchCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTableViewiCloudSwitchCell.swift; sourceTree = "<group>"; };
ED70D5582D5396F300738C1E /* SearchItemType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SearchItemType.h; sourceTree = "<group>"; };
ED70D5592D5396F300738C1E /* SearchResult.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SearchResult.h; sourceTree = "<group>"; };
ED70D55A2D5396F300738C1E /* SearchResult.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SearchResult.mm; sourceTree = "<group>"; };
ED70D55B2D5396F300738C1E /* SearchResult+Core.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SearchResult+Core.h"; sourceTree = "<group>"; };
ED70D57B2D539A2500738C1E /* MapPassthroughView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapPassthroughView.swift; sourceTree = "<group>"; };
ED70D57C2D539A2500738C1E /* ModalScreenPresentationStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalScreenPresentationStep.swift; sourceTree = "<group>"; };
ED70D57D2D539A2500738C1E /* SearchOnMapModalPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapModalPresentationController.swift; sourceTree = "<group>"; };
ED70D57E2D539A2500738C1E /* SearchOnMapModalTransitionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapModalTransitionManager.swift; sourceTree = "<group>"; };
ED70D57F2D539A2500738C1E /* SideMenuDismissalAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuDismissalAnimator.swift; sourceTree = "<group>"; };
ED70D5802D539A2500738C1E /* SideMenuPresentationAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuPresentationAnimator.swift; sourceTree = "<group>"; };
ED70D5822D539A2500738C1E /* PlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderView.swift; sourceTree = "<group>"; };
ED70D5832D539A2500738C1E /* SearchOnMapInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapInteractor.swift; sourceTree = "<group>"; };
ED70D5842D539A2500738C1E /* SearchOnMapManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapManager.swift; sourceTree = "<group>"; };
ED70D5852D539A2500738C1E /* SearchOnMapModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapModels.swift; sourceTree = "<group>"; };
ED70D5862D539A2500738C1E /* SearchOnMapPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapPresenter.swift; sourceTree = "<group>"; };
ED70D5872D539A2500738C1E /* SearchOnMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapViewController.swift; sourceTree = "<group>"; };
ED77556D2C2C490B0051E656 /* UIAlertController+openInAppActionSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+openInAppActionSheet.swift"; sourceTree = "<group>"; };
ED79A5AA2BD7AA9C00952D1F /* LoadingOverlayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingOverlayViewController.swift; sourceTree = "<group>"; };
ED79A5AC2BD7BA0F00952D1F /* UIApplication+LoadingOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+LoadingOverlay.swift"; sourceTree = "<group>"; };
@ -1471,6 +1482,7 @@
ED79A5D02BDF8D6100952D1F /* LocalDirectoryMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalDirectoryMonitor.swift; sourceTree = "<group>"; };
ED7CCC4E2C1362E300E2A737 /* FileType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileType.swift; sourceTree = "<group>"; };
ED808D0E2C38407800D52585 /* CircleImageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleImageButton.swift; sourceTree = "<group>"; };
ED810EC42D566E9B00ECDE2C /* SearchOnMapTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapTests.swift; sourceTree = "<group>"; };
ED8270EF2C2071A3005966DA /* SettingsTableViewDetailedSwitchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTableViewDetailedSwitchCell.swift; sourceTree = "<group>"; };
ED83880E2D54DEA4002A0536 /* UIImage+FilledWithColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+FilledWithColor.swift"; sourceTree = "<group>"; };
ED914AB12D35063A00973C45 /* TextColorStyleSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextColorStyleSheet.swift; sourceTree = "<group>"; };
@ -1488,7 +1500,6 @@
EDC4E3482C5D1BEF009286A2 /* RecentlyDeletedCategoriesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentlyDeletedCategoriesViewModel.swift; sourceTree = "<group>"; };
EDC4E3492C5D1BEF009286A2 /* RecentlyDeletedTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentlyDeletedTableViewCell.swift; sourceTree = "<group>"; };
EDCA7CDE2D317DF9003366CE /* StyleSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyleSheet.swift; sourceTree = "<group>"; };
EDDE060D2D6CAEAF000C328A /* SearchHistoryViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SearchHistoryViewController.xib; sourceTree = "<group>"; };
EDE243D52B6CF3980057369B /* AboutController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutController.swift; sourceTree = "<group>"; };
EDE243E02B6D3EA00057369B /* InfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoView.swift; sourceTree = "<group>"; };
EDE243E42B6D3F400057369B /* OSMView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSMView.swift; sourceTree = "<group>"; };
@ -1664,17 +1675,9 @@
F6E2FCBE1E097B9F0083EBEC /* MWMPlacePageOpeningHoursDayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMPlacePageOpeningHoursDayView.h; sourceTree = "<group>"; };
F6E2FCBF1E097B9F0083EBEC /* MWMPlacePageOpeningHoursDayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MWMPlacePageOpeningHoursDayView.m; sourceTree = "<group>"; };
F6E2FCC01E097B9F0083EBEC /* MWMPlacePageOpeningHoursWeekDayView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MWMPlacePageOpeningHoursWeekDayView.xib; sourceTree = "<group>"; };
F6E2FCF41E097B9F0083EBEC /* MWMSearchContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMSearchContentView.h; sourceTree = "<group>"; };
F6E2FCF51E097B9F0083EBEC /* MWMSearchContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MWMSearchContentView.m; sourceTree = "<group>"; };
F6E2FCF81E097B9F0083EBEC /* MWMSearchManager+Layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MWMSearchManager+Layout.h"; sourceTree = "<group>"; };
F6E2FCF91E097B9F0083EBEC /* MWMSearchManager+Layout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MWMSearchManager+Layout.m"; sourceTree = "<group>"; };
F6E2FCFA1E097B9F0083EBEC /* MWMSearchManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMSearchManager.h; sourceTree = "<group>"; };
F6E2FCFB1E097B9F0083EBEC /* MWMSearchManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMSearchManager.mm; sourceTree = "<group>"; };
F6E2FCFC1E097B9F0083EBEC /* MWMSearchNoResults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMSearchNoResults.h; sourceTree = "<group>"; };
F6E2FCFD1E097B9F0083EBEC /* MWMSearchNoResults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MWMSearchNoResults.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
F6E2FCFE1E097B9F0083EBEC /* MWMSearchNoResults.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MWMSearchNoResults.xib; sourceTree = "<group>"; };
F6E2FD011E097B9F0083EBEC /* MWMSearchView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MWMSearchView.xib; sourceTree = "<group>"; };
F6E2FD231E097BA00083EBEC /* MWMSearchTabbedViewProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMSearchTabbedViewProtocol.h; sourceTree = "<group>"; };
F6E2FD291E097BA00083EBEC /* MWMSearchCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMSearchCell.h; sourceTree = "<group>"; };
F6E2FD2A1E097BA00083EBEC /* MWMSearchCell.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMSearchCell.mm; sourceTree = "<group>"; };
F6E2FD2B1E097BA00083EBEC /* MWMSearchCommonCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMSearchCommonCell.h; sourceTree = "<group>"; };
@ -1683,11 +1686,6 @@
F6E2FD2E1E097BA00083EBEC /* MWMSearchSuggestionCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMSearchSuggestionCell.h; sourceTree = "<group>"; };
F6E2FD2F1E097BA00083EBEC /* MWMSearchSuggestionCell.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMSearchSuggestionCell.mm; sourceTree = "<group>"; };
F6E2FD301E097BA00083EBEC /* MWMSearchSuggestionCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MWMSearchSuggestionCell.xib; sourceTree = "<group>"; };
F6E2FD311E097BA00083EBEC /* MWMSearchTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMSearchTableView.h; sourceTree = "<group>"; };
F6E2FD321E097BA00083EBEC /* MWMSearchTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MWMSearchTableView.m; sourceTree = "<group>"; };
F6E2FD331E097BA00083EBEC /* MWMSearchTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMSearchTableViewController.h; sourceTree = "<group>"; };
F6E2FD341E097BA00083EBEC /* MWMSearchTableViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = MWMSearchTableViewController.mm; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
F6E2FD351E097BA00083EBEC /* MWMSearchTableViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MWMSearchTableViewController.xib; sourceTree = "<group>"; };
F6E2FD381E097BA00083EBEC /* SettingsTableViewLinkCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTableViewLinkCell.swift; sourceTree = "<group>"; };
F6E2FD391E097BA00083EBEC /* SettingsTableViewSelectableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTableViewSelectableCell.swift; sourceTree = "<group>"; };
F6E2FD3A1E097BA00083EBEC /* SettingsTableViewSwitchCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTableViewSwitchCell.swift; sourceTree = "<group>"; };
@ -3180,6 +3178,7 @@
ED1ADA312BC6B19E0029209F /* Tests */ = {
isa = PBXGroup;
children = (
ED810EC02D566E6F00ECDE2C /* UI */,
EDC4E3442C5D1BD3009286A2 /* Bookmarks */,
4B4153B82BF970B800EE4B02 /* Classes */,
4B4153B62BF9709100EE4B02 /* Core */,
@ -3231,6 +3230,34 @@
path = Products;
sourceTree = "<group>";
};
ED70D5812D539A2500738C1E /* Presentation */ = {
isa = PBXGroup;
children = (
ED70D57B2D539A2500738C1E /* MapPassthroughView.swift */,
ED70D57C2D539A2500738C1E /* ModalScreenPresentationStep.swift */,
ED70D57D2D539A2500738C1E /* SearchOnMapModalPresentationController.swift */,
ED70D57E2D539A2500738C1E /* SearchOnMapModalTransitionManager.swift */,
ED70D57F2D539A2500738C1E /* SideMenuDismissalAnimator.swift */,
ED70D5802D539A2500738C1E /* SideMenuPresentationAnimator.swift */,
);
path = Presentation;
sourceTree = "<group>";
};
ED70D5882D539A2500738C1E /* SearchOnMap */ = {
isa = PBXGroup;
children = (
ED70D5812D539A2500738C1E /* Presentation */,
ED70D5822D539A2500738C1E /* PlaceholderView.swift */,
ED70D5832D539A2500738C1E /* SearchOnMapInteractor.swift */,
ED70D5842D539A2500738C1E /* SearchOnMapManager.swift */,
ED70D5852D539A2500738C1E /* SearchOnMapModels.swift */,
ED70D5862D539A2500738C1E /* SearchOnMapPresenter.swift */,
ED70D5872D539A2500738C1E /* SearchOnMapViewController.swift */,
ED5BAF4A2D688F5A0088D7B1 /* SearchOnMapHeaderView.swift */,
);
path = SearchOnMap;
sourceTree = "<group>";
};
ED79A5A92BD7AA7500952D1F /* LoadingOverlay */ = {
isa = PBXGroup;
children = (
@ -3253,6 +3280,22 @@
path = iCloud;
sourceTree = "<group>";
};
ED810EC02D566E6F00ECDE2C /* UI */ = {
isa = PBXGroup;
children = (
ED810EC32D566E7600ECDE2C /* SearchOnMapTests */,
);
path = UI;
sourceTree = "<group>";
};
ED810EC32D566E7600ECDE2C /* SearchOnMapTests */ = {
isa = PBXGroup;
children = (
ED810EC42D566E9B00ECDE2C /* SearchOnMapTests.swift */,
);
path = SearchOnMapTests;
sourceTree = "<group>";
};
ED9857022C4ECFFC00694F6C /* MailComposer */ = {
isa = PBXGroup;
children = (
@ -3822,24 +3865,13 @@
F6E2FCE11E097B9F0083EBEC /* Search */ = {
isa = PBXGroup;
children = (
F6E2FCF41E097B9F0083EBEC /* MWMSearchContentView.h */,
F6E2FCF51E097B9F0083EBEC /* MWMSearchContentView.m */,
F6E2FCF81E097B9F0083EBEC /* MWMSearchManager+Layout.h */,
F6E2FCF91E097B9F0083EBEC /* MWMSearchManager+Layout.m */,
F6E2FCFA1E097B9F0083EBEC /* MWMSearchManager.h */,
F6E2FCFB1E097B9F0083EBEC /* MWMSearchManager.mm */,
ED70D5882D539A2500738C1E /* SearchOnMap */,
F6E2FCFC1E097B9F0083EBEC /* MWMSearchNoResults.h */,
F6E2FCFD1E097B9F0083EBEC /* MWMSearchNoResults.m */,
F6E2FCFE1E097B9F0083EBEC /* MWMSearchNoResults.xib */,
9977E69B247BFB510073780C /* SearchTextField.swift */,
F6E2FD011E097B9F0083EBEC /* MWMSearchView.xib */,
F6E2FD021E097B9F0083EBEC /* Tabs */,
F6E2FD281E097BA00083EBEC /* TableView */,
34845DB11E165E24003D55B9 /* SearchNoResultsViewController.swift */,
349D1ABA1E2D05EF004A2006 /* SearchBar.swift */,
34B3806B1F1E46E20087D65B /* MWMSearchManagerState.h */,
347AD8081F28B4E6007ACB68 /* MWMSearchManagerObserver.h */,
990F33B524BC915200D0F426 /* SearchActionBarView.swift */,
475ED78724C7D0F30063ADC7 /* ValueStepperView.swift */,
);
path = Search;
@ -3850,7 +3882,6 @@
children = (
F6E2FD091E097B9F0083EBEC /* CategoriesTab */,
F6E2FD0F1E097B9F0083EBEC /* HistoryTab */,
F6E2FD231E097BA00083EBEC /* MWMSearchTabbedViewProtocol.h */,
337F98A521D37B7400C8AC27 /* SearchTabViewController.swift */,
);
path = Tabs;
@ -3868,7 +3899,6 @@
F6E2FD0F1E097B9F0083EBEC /* HistoryTab */ = {
isa = PBXGroup;
children = (
EDDE060D2D6CAEAF000C328A /* SearchHistoryViewController.xib */,
337F98B721D3D67E00C8AC27 /* SearchHistoryCell.swift */,
337F98B321D3C9F200C8AC27 /* SearchHistoryViewController.swift */,
);
@ -3886,11 +3916,6 @@
F6E2FD2E1E097BA00083EBEC /* MWMSearchSuggestionCell.h */,
F6E2FD2F1E097BA00083EBEC /* MWMSearchSuggestionCell.mm */,
F6E2FD301E097BA00083EBEC /* MWMSearchSuggestionCell.xib */,
F6E2FD311E097BA00083EBEC /* MWMSearchTableView.h */,
F6E2FD321E097BA00083EBEC /* MWMSearchTableView.m */,
F6E2FD331E097BA00083EBEC /* MWMSearchTableViewController.h */,
F6E2FD341E097BA00083EBEC /* MWMSearchTableViewController.mm */,
F6E2FD351E097BA00083EBEC /* MWMSearchTableViewController.xib */,
);
path = TableView;
sourceTree = "<group>";
@ -4266,7 +4291,6 @@
6741A9991BF340DE002C974C /* MWMAlertViewController.xib in Resources */,
4501B1942077C35A001B9173 /* resources-xxxhdpi_light in Resources */,
3467CEB7202C6FA900D3C670 /* BMCNotificationsCell.xib in Resources */,
EDDE060E2D6CAEAF000C328A /* SearchHistoryViewController.xib in Resources */,
4761BE2B252D3DB900EE2DE4 /* SubgroupCell.xib in Resources */,
99F9A0E72462CA1700AE21E0 /* DownloadAllView.xib in Resources */,
349D1AD51E2E325B004A2006 /* BottomMenuItemCell.xib in Resources */,
@ -4338,9 +4362,7 @@
F6E2FF391E097BA00083EBEC /* MWMSearchSuggestionCell.xib in Resources */,
34AB66231FC5AA330078E451 /* MWMiPadRoutePreview.xib in Resources */,
BB25B1A71FB32767007276FA /* transit_colors.txt in Resources */,
F6E2FF421E097BA00083EBEC /* MWMSearchTableViewController.xib in Resources */,
34AB66681FC5AA330078E451 /* TransportTransitPedestrian.xib in Resources */,
F6E2FEEE1E097BA00083EBEC /* MWMSearchView.xib in Resources */,
4501B1952077C35A001B9173 /* resources-xxxhdpi_dark in Resources */,
F6D67CDE2062BBA60032FD38 /* MWMBCCreateCategoryAlert.xib in Resources */,
3490D2E31CE9DD2500D0B838 /* MWMSideButtonsView.xib in Resources */,
@ -4431,7 +4453,6 @@
34D3AFF61E37A36A004100F9 /* UICollectionView+Cells.swift in Sources */,
4767CDA420AAF66B00BD8166 /* NSAttributedString+HTML.swift in Sources */,
6741A9A91BF340DE002C974C /* MWMDefaultAlert.mm in Sources */,
990F33B624BC915200D0F426 /* SearchActionBarView.swift in Sources */,
99514BBA23E82B450085D3A7 /* ElevationProfileViewController.swift in Sources */,
340708781F2B5D6C00029ECC /* DimBackground.swift in Sources */,
3490D2DF1CE9DD2500D0B838 /* MWMSideButtons.mm in Sources */,
@ -4483,8 +4504,6 @@
6741A9B81BF340DE002C974C /* MapViewController.mm in Sources */,
34AB662C1FC5AA330078E451 /* RouteManagerViewModel.swift in Sources */,
3404F48B202894EA0090E401 /* BMCViewController.swift in Sources */,
349D1ABC1E2D05EF004A2006 /* SearchBar.swift in Sources */,
F6E2FF3F1E097BA00083EBEC /* MWMSearchTableViewController.mm in Sources */,
F6E2FDE01E097BA00083EBEC /* MWMEditorViewController.mm in Sources */,
6741A9C01BF340DE002C974C /* MWMTextView.m in Sources */,
F6E2FDB61E097BA00083EBEC /* MWMEditorAdditionalNamesHeader.m in Sources */,
@ -4522,7 +4541,6 @@
F6791B141C43DF0B007A8A6E /* MWMStartButton.m in Sources */,
9977E6A12480E1EE0073780C /* BottomMenuLayerButton.swift in Sources */,
471527372491C20500E91BBA /* SelectBookmarkGroupViewController.swift in Sources */,
F6E2FEDF1E097BA00083EBEC /* MWMSearchManager+Layout.m in Sources */,
F64D9CA01C899C350063FA30 /* MWMEditorViralAlert.mm in Sources */,
34AC8FD11EFC02C000E7F910 /* MWMRoutePoint.mm in Sources */,
CDB4D5012231412900104869 /* ListTemplateBuilder.swift in Sources */,
@ -4556,6 +4574,7 @@
ED43B8BD2C12063500D07BAA /* DocumentPicker.swift in Sources */,
470E1674252AD7F2002D201A /* BookmarksListInfoViewController.swift in Sources */,
47B9065521C7FA400079C85E /* NSString+MD5.m in Sources */,
ED5BAF4B2D688F5B0088D7B1 /* SearchOnMapHeaderView.swift in Sources */,
CDB4D5022231412900104869 /* SettingsTemplateBuilder.swift in Sources */,
993DF10A23F6BDB100AC231A /* UISwitchRenderer.swift in Sources */,
99C9642C2428C0F700E41723 /* PlacePageHeaderBuilder.swift in Sources */,
@ -4683,7 +4702,6 @@
3472B5CF200F4A2B00DC6CD5 /* BackgroundFetchTask.swift in Sources */,
477219052243E79500E5B227 /* DrivingOptionsViewController.swift in Sources */,
CDB4D4E4222E8FF600104869 /* CarPlayService.swift in Sources */,
F6E2FF3C1E097BA00083EBEC /* MWMSearchTableView.m in Sources */,
F6E2FF661E097BA00083EBEC /* MWMTTSSettingsViewController.mm in Sources */,
3454D7C21E07F045004AF2AD /* NSString+Categories.m in Sources */,
ED2D74662D1435A600660FBF /* LiveActivityManager.swift in Sources */,
@ -4735,12 +4753,23 @@
CD9AD96C2281B56900EC174A /* CPViewPortState.swift in Sources */,
EDE243DD2B6D2E640057369B /* AboutController.swift in Sources */,
3404755C1E081A4600C92850 /* MWMLocationManager.mm in Sources */,
ED70D5892D539A2500738C1E /* SearchOnMapViewController.swift in Sources */,
ED70D58A2D539A2500738C1E /* SearchOnMapModels.swift in Sources */,
ED70D58B2D539A2500738C1E /* SearchOnMapModalTransitionManager.swift in Sources */,
ED70D58C2D539A2500738C1E /* SearchOnMapPresenter.swift in Sources */,
ED70D58D2D539A2500738C1E /* ModalScreenPresentationStep.swift in Sources */,
ED70D58E2D539A2500738C1E /* SideMenuPresentationAnimator.swift in Sources */,
ED70D58F2D539A2500738C1E /* SearchOnMapInteractor.swift in Sources */,
ED70D5902D539A2500738C1E /* SearchOnMapModalPresentationController.swift in Sources */,
ED70D5912D539A2500738C1E /* MapPassthroughView.swift in Sources */,
ED70D5922D539A2500738C1E /* PlaceholderView.swift in Sources */,
ED70D5932D539A2500738C1E /* SearchOnMapManager.swift in Sources */,
ED70D5942D539A2500738C1E /* SideMenuDismissalAnimator.swift in Sources */,
3454D7BC1E07F045004AF2AD /* CLLocation+Mercator.mm in Sources */,
47E3C7272111E5A8008B3B27 /* AlertPresentationController.swift in Sources */,
CDCA27812243F59800167D87 /* CarPlayRouter.swift in Sources */,
34F5E0D41E3F254800B1C415 /* UIView+Hierarchy.swift in Sources */,
6741AA0B1BF340DE002C974C /* MWMMapViewControlsManager.mm in Sources */,
F6E2FED91E097BA00083EBEC /* MWMSearchContentView.m in Sources */,
EDFDFB4C2B722C9C0013A44C /* InfoTableViewCell.swift in Sources */,
47CA68F8250F8AB700671019 /* BookmarksListSectionHeader.swift in Sources */,
F6BD1D211CA412920047B8E8 /* MWMOsmAuthAlert.mm in Sources */,
@ -4767,7 +4796,6 @@
4767CDA820AB401000BD8166 /* LinkTextView.swift in Sources */,
34763EE71F2F392300F4D2D3 /* MWMTextToSpeech.mm in Sources */,
998927402449ECC200260CE2 /* BottomMenuItemCell.swift in Sources */,
F6E2FEE21E097BA00083EBEC /* MWMSearchManager.mm in Sources */,
F6E2FE221E097BA00083EBEC /* MWMOpeningHoursEditorViewController.mm in Sources */,
ED79A5D72BDF8D6100952D1F /* SynchronizationStateResolver.swift in Sources */,
999FC12B23ABB4B800B0E6F9 /* FontStyleSheet.swift in Sources */,
@ -4794,7 +4822,6 @@
34B127EA1FBDD410008713D9 /* MWMRouterTransitStepInfo.mm in Sources */,
34D3B02A1E389D05004100F9 /* MWMEditorAdditionalNameTableViewCell.m in Sources */,
993DF10523F6BDB100AC231A /* UINavigationItem+StyleStyle.swift in Sources */,
9977E69C247BFB510073780C /* SearchTextField.swift in Sources */,
4726254921C27D4B00C7BAAD /* PlacePageDescriptionViewController.swift in Sources */,
039371B62C5B68CD00708377 /* UIFont+monospaced.swift in Sources */,
447DB4BC2BA78282000DF4C2 /* MWMOsmReauthAlert.mm in Sources */,
@ -4881,6 +4908,7 @@
EDF838BE2C00B9D0007E4E67 /* LocalDirectoryMonitorDelegateMock.swift in Sources */,
EDC4E3692C5E6F5B009286A2 /* MockRecentlyDeletedCategoriesManager.swift in Sources */,
EDF838BF2C00B9D0007E4E67 /* SynchronizationStateManagerTests.swift in Sources */,
ED810EC52D566E9B00ECDE2C /* SearchOnMapTests.swift in Sources */,
4B83AE4B2C2E642100B0C3BC /* TTSTesterTest.m in Sources */,
EDF838C22C00B9D6007E4E67 /* MetadataItemStubs.swift in Sources */,
EDC4E3612C5E2576009286A2 /* RecentlyDeletedCategoriesViewModelTests.swift in Sources */,

View file

@ -0,0 +1,262 @@
import XCTest
@testable import Organic_Maps__Debug_
final class SearchOnMapTests: XCTestCase {
private var presenter: SearchOnMapPresenter!
private var interactor: SearchOnMapInteractor!
private var view: SearchOnMapViewMock!
private var searchManager: SearchManagerMock.Type!
private var currentState: SearchOnMapState = .searching
override func setUp() {
super.setUp()
searchManager = SearchManagerMock.self
presenter = SearchOnMapPresenter(transitionManager: SearchOnMapModalTransitionManager(),
isRouting: false,
didChangeState: { [weak self] in self?.currentState = $0 })
interactor = SearchOnMapInteractor(presenter: presenter, searchManager: searchManager)
view = SearchOnMapViewMock()
presenter.view = view
}
override func tearDown() {
presenter = nil
interactor = nil
view = nil
searchManager.results = .empty
searchManager = nil
super.tearDown()
}
func test_GivenViewIsLoading_WhenViewLoads_ThenShowsHistoryAndCategory() {
interactor.handle(.openSearch)
XCTAssertEqual(currentState, .searching)
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
XCTAssertEqual(view.viewModel.contentState, .historyAndCategory)
XCTAssertEqual(view.viewModel.searchingText, nil)
XCTAssertEqual(view.viewModel.isTyping, true)
}
func test_GivenInitialState_WhenSelectCategory_ThenUpdateSearchResultsAndShowMap() {
interactor.handle(.openSearch)
let searchText = SearchOnMap.SearchText("category")
interactor.handle(.didSelectText(searchText, isCategory: true))
XCTAssertEqual(view.viewModel.presentationStep, .halfScreen)
XCTAssertEqual(view.viewModel.contentState, .searching)
XCTAssertEqual(view.viewModel.searchingText, searchText.text)
XCTAssertEqual(view.viewModel.isTyping, false)
let results = SearchResult.stubResults()
searchManager.results = results
XCTAssertEqual(currentState, .searching)
XCTAssertEqual(view.viewModel.presentationStep, .halfScreen)
XCTAssertEqual(view.viewModel.contentState, .results(results))
XCTAssertEqual(view.viewModel.searchingText, nil)
XCTAssertEqual(view.viewModel.isTyping, false)
}
func test_GivenInitialState_WhenTypeText_ThenUpdateSearchResults() {
interactor.handle(.openSearch)
let searchText = SearchOnMap.SearchText("text")
interactor.handle(.didType(searchText))
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
XCTAssertEqual(view.viewModel.contentState, .searching)
XCTAssertEqual(view.viewModel.searchingText, nil)
XCTAssertEqual(view.viewModel.isTyping, true)
let results = SearchResult.stubResults()
searchManager.results = results
XCTAssertEqual(currentState, .searching)
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
XCTAssertEqual(view.viewModel.contentState, .results(results))
XCTAssertEqual(view.viewModel.searchingText, nil)
XCTAssertEqual(view.viewModel.isTyping, true)
}
func test_GivenInitialState_WhenTapSearch_ThenUpdateSearchResultsAndShowMap() {
interactor.handle(.openSearch)
let searchText = SearchOnMap.SearchText("text")
interactor.handle(.didType(searchText))
let results = SearchResult.stubResults()
searchManager.results = results
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
XCTAssertEqual(view.viewModel.contentState, .results(results))
XCTAssertEqual(view.viewModel.searchingText, nil)
XCTAssertEqual(view.viewModel.isTyping, true)
interactor.handle(.searchButtonDidTap(searchText))
XCTAssertEqual(currentState, .searching)
XCTAssertEqual(view.viewModel.presentationStep, .halfScreen)
XCTAssertEqual(view.viewModel.contentState, .results(results))
XCTAssertEqual(view.viewModel.searchingText, nil)
XCTAssertEqual(view.viewModel.isTyping, false)
}
func test_GivenSearchIsOpened_WhenMapIsDragged_ThenCollapseSearchScreen() {
interactor.handle(.openSearch)
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
interactor.handle(.didStartDraggingMap)
XCTAssertEqual(view.viewModel.presentationStep, .compact)
}
func test_GivenSearchIsOpened_WhenModalPresentationScreenIsDragged_ThenDisableTyping() {
interactor.handle(.openSearch)
XCTAssertEqual(view.viewModel.isTyping, true)
interactor.handle(.didStartDraggingSearch)
XCTAssertEqual(view.viewModel.isTyping, false)
}
func test_GivenResultsOnScreen_WhenSelectResult_ThenHideSearch() {
interactor.handle(.openSearch)
XCTAssertEqual(view.viewModel.isTyping, true)
let searchText = SearchOnMap.SearchText("text")
interactor.handle(.didType(searchText))
let results = SearchResult.stubResults()
searchManager.results = results
interactor.handle(.didSelectResult(results[0], atIndex: 0, withSearchText: searchText))
XCTAssertEqual(currentState, .hidden)
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
}
func test_GivenSearchIsActive_WhenSelectPlaceOnMap_ThenHideSearch() {
interactor.handle(.openSearch)
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
interactor.handle(.didSelectPlaceOnMap)
if isIPad {
XCTAssertNotEqual(view.viewModel.presentationStep, .hidden)
} else {
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
}
}
func test_GivenSearchIsHidden_WhenPPDeselected_ThenShowSearch() {
interactor.handle(.openSearch)
XCTAssertEqual(view.viewModel.isTyping, true)
let searchText = SearchOnMap.SearchText("text")
interactor.handle(.didType(searchText))
let results = SearchResult.stubResults()
searchManager.results = results
interactor.handle(.didSelectResult(results[0], atIndex: 0, withSearchText: searchText))
XCTAssertEqual(currentState, .hidden)
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
interactor.handle(.didDeselectPlaceOnMap)
XCTAssertEqual(currentState, .searching)
XCTAssertEqual(view.viewModel.presentationStep, .halfScreen)
}
func test_GivenSearchIsOpen_WhenCloseSearch_ThenHideSearch() {
interactor.handle(.openSearch)
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
interactor.handle(.closeSearch)
XCTAssertEqual(currentState, .closed)
}
func test_GivenSearchHasText_WhenClearSearch_ThenShowHistoryAndCategory() {
interactor.handle(.openSearch)
let searchText = SearchOnMap.SearchText("text")
interactor.handle(.didType(searchText))
interactor.handle(.clearButtonDidTap)
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
XCTAssertEqual(view.viewModel.contentState, .historyAndCategory)
XCTAssertEqual(view.viewModel.searchingText, "")
XCTAssertEqual(view.viewModel.isTyping, true)
}
func test_GivenSearchExecuted_WhenNoResults_ThenShowNoResults() {
interactor.handle(.openSearch)
let searchText = SearchOnMap.SearchText("text")
interactor.handle(.didType(searchText))
searchManager.results = SearchOnMap.SearchResults([])
interactor.onSearchCompleted()
XCTAssertEqual(view.viewModel.contentState, .noResults)
}
func test_GivenSearchIsActive_WhenSelectSuggestion_ThenSearchAgain() {
interactor.handle(.openSearch)
let searchText = SearchOnMap.SearchText("old search")
interactor.handle(.didType(searchText))
let suggestion = SearchResult(titleText: "", type: .suggestion, suggestion: "suggestion")
interactor.handle(.didSelectResult(suggestion, atIndex: 0, withSearchText: searchText))
XCTAssertEqual(view.viewModel.searchingText, "suggestion")
XCTAssertEqual(view.viewModel.contentState, .searching)
}
}
// MARK: - Mocks
private class SearchOnMapViewMock: SearchOnMapView {
var viewModel: SearchOnMap.ViewModel = .initial
var scrollViewDelegate: (any SearchOnMapScrollViewDelegate)?
func render(_ viewModel: SearchOnMap.ViewModel) {
self.viewModel = viewModel
}
}
private class SearchManagerMock: SearchManager {
static var observers = ListenerContainer<MWMSearchObserver>()
static var results = SearchOnMap.SearchResults.empty {
didSet {
observers.forEach { observer in
observer.onSearchCompleted?()
}
}
}
static func add(_ observer: any MWMSearchObserver) {
self.observers.addListener(observer)
}
static func remove(_ observer: any MWMSearchObserver) {
self.observers.removeListener(observer)
}
static func saveQuery(_ query: String, forInputLocale inputLocale: String) {}
static func searchQuery(_ query: String, forInputLocale inputLocale: String, withCategory isCategory: Bool) {}
static func showResult(at index: UInt) {}
static func showEverywhereSearchResultsOnMap() {}
static func showViewportSearchResultsOnMap() {}
static func clear() {}
static func getResults() -> [SearchResult] { results.results }
}
private extension SearchResult {
static func stubResults() -> SearchOnMap.SearchResults {
SearchOnMap.SearchResults([
SearchResult(),
SearchResult(),
SearchResult()
])
}
}

View file

@ -11,22 +11,19 @@ class BottomTabBarInteractor {
private weak var viewController: UIViewController?
private weak var mapViewController: MapViewController?
private weak var controlsManager: MWMMapViewControlsManager?
private weak var searchManager = MWMSearchManager.manager()
private let searchManager: SearchOnMapManager
init(viewController: UIViewController, mapViewController: MapViewController, controlsManager: MWMMapViewControlsManager) {
self.viewController = viewController
self.mapViewController = mapViewController
self.controlsManager = controlsManager
self.searchManager = mapViewController.searchManager
}
}
extension BottomTabBarInteractor: BottomTabBarInteractorProtocol {
func openSearch() {
if searchManager?.state == .hidden {
searchManager?.state = .default
} else {
searchManager?.state = .hidden
}
searchManager.isSearching ? searchManager.close() : searchManager.startSearching(isRouting: false)
}
func openHelp() {

View file

@ -32,8 +32,6 @@ class BottomTabBarViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
presenter.configure()
MWMSearchManager.add(self)
}
override func viewWillAppear(_ animated: Bool) {
@ -45,10 +43,6 @@ class BottomTabBarViewController: UIViewController {
updateBadge()
}
deinit {
MWMSearchManager.remove(self)
}
static func updateAvailableArea(_ frame: CGRect) {
BottomTabBarViewController.controller?.updateAvailableArea(frame)
}
@ -119,11 +113,3 @@ private extension BottomTabBarViewController {
UserDefaults.standard.set(true, forKey: kUDDidShowFirstTimeRoutingEducationalHint)
}
}
// MARK: - MWMSearchManagerObserver
extension BottomTabBarViewController: MWMSearchManagerObserver {
func onSearchManagerStateChanged() {
let state = MWMSearchManager.manager().state;
self.searchButton.isSelected = state != .hidden
}
}

View file

@ -1,3 +0,0 @@
@interface MWMSearchContentView : SolidTouchView
@end

View file

@ -1,14 +0,0 @@
#import "MWMSearchContentView.h"
@implementation MWMSearchContentView
- (void)layoutSubviews
{
[self.subviews enumerateObjectsUsingBlock:^(UIView * view, NSUInteger idx, BOOL * stop)
{
view.frame = self.bounds;
}];
[super layoutSubviews];
}
@end

View file

@ -1,8 +0,0 @@
#import "MWMSearchManager.h"
@interface MWMSearchManager (Layout)
- (void)layoutTopViews;
- (void)removeKeyboardObservers;
@end

View file

@ -1,120 +0,0 @@
#import <CoreApi/MWMCommon.h>
#import "MWMSearchManager+Layout.h"
#import "MapViewController.h"
static CGFloat const changeModeViewOffsetNormal = -24;
static CGFloat const changeModeViewOffsetKeyboard = -12;
@interface MWMSearchManager ()
@property(nonatomic) IBOutlet UIView *searchBarView;
@property(nonatomic) IBOutlet UIView *actionBarView;
@property(nonatomic) IBOutlet UIView *contentView;
@property(nonatomic) NSLayoutConstraint *contentViewTopHidden;
@property(nonatomic) NSLayoutConstraint *contentViewBottomHidden;
@property(nonatomic) NSLayoutConstraint *actionBarViewBottomKeyboard;
@property(nonatomic) NSLayoutConstraint *actionBarViewBottomNormal;
@property(weak, nonatomic, readonly) UIView *searchViewContainer;
@end
@implementation MWMSearchManager (Layout)
- (void)layoutTopViews {
UIView *searchBarView = self.searchBarView;
UIView *changeModeView = self.actionBarView;
UIView *contentView = self.contentView;
UIView *parentView = self.searchViewContainer;
searchBarView.translatesAutoresizingMaskIntoConstraints = NO;
changeModeView.translatesAutoresizingMaskIntoConstraints = NO;
contentView.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutXAxisAnchor *leadingAnchor = parentView.leadingAnchor;
NSLayoutXAxisAnchor *trailingAnchor = parentView.trailingAnchor;
NSLayoutYAxisAnchor *topAnchor = parentView.safeAreaLayoutGuide.topAnchor;
NSLayoutYAxisAnchor *bottomAnchor = parentView.safeAreaLayoutGuide.bottomAnchor;
[searchBarView.topAnchor constraintEqualToAnchor:topAnchor].active = YES;
[searchBarView.leadingAnchor constraintEqualToAnchor:leadingAnchor].active = YES;
if (IPAD)
[searchBarView.widthAnchor constraintEqualToConstant:kWidthForiPad].active = YES;
else
[searchBarView.trailingAnchor constraintEqualToAnchor:trailingAnchor].active = YES;
[changeModeView.centerXAnchor constraintEqualToAnchor:parentView.centerXAnchor].active = YES;
self.actionBarViewBottomNormal = [changeModeView.bottomAnchor constraintEqualToAnchor:bottomAnchor
constant:changeModeViewOffsetNormal];
self.actionBarViewBottomNormal.priority = UILayoutPriorityDefaultLow + 10;
self.actionBarViewBottomNormal.active = YES;
self.actionBarViewBottomKeyboard = [changeModeView.bottomAnchor constraintEqualToAnchor:parentView.bottomAnchor
constant:changeModeViewOffsetKeyboard];
self.actionBarViewBottomKeyboard.priority = UILayoutPriorityDefaultLow;
self.actionBarViewBottomKeyboard.active = YES;
NSLayoutConstraint *contentViewTop = [contentView.topAnchor constraintEqualToAnchor:searchBarView.bottomAnchor];
contentViewTop.priority = UILayoutPriorityDefaultLow + 10;
contentViewTop.active = YES;
NSLayoutConstraint *contentViewBottom = [contentView.bottomAnchor constraintEqualToAnchor:parentView.bottomAnchor];
contentViewBottom.priority = UILayoutPriorityDefaultLow + 10;
contentViewBottom.active = YES;
self.contentViewTopHidden = [contentView.topAnchor constraintEqualToAnchor:parentView.bottomAnchor];
self.contentViewTopHidden.priority = UILayoutPriorityDefaultLow;
self.contentViewTopHidden.active = YES;
self.contentViewBottomHidden = [contentView.heightAnchor constraintEqualToAnchor:parentView.heightAnchor];
self.contentViewBottomHidden.priority = UILayoutPriorityDefaultLow;
self.contentViewBottomHidden.active = YES;
[contentView.leadingAnchor constraintEqualToAnchor:searchBarView.leadingAnchor].active = YES;
[contentView.trailingAnchor constraintEqualToAnchor:searchBarView.trailingAnchor].active = YES;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)removeKeyboardObservers {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - keyboard movements
- (void)keyboardWillShow:(NSNotification *)notification {
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
CGFloat offset = IPAD ? changeModeViewOffsetNormal : changeModeViewOffsetKeyboard;
if (self.actionBarView.isHidden) {
self.actionBarViewBottomKeyboard.constant = -keyboardSize.height + offset;
self.actionBarViewBottomKeyboard.priority = UILayoutPriorityDefaultHigh;
} else {
[UIView animateWithDuration:kDefaultAnimationDuration
animations:^{
self.actionBarViewBottomKeyboard.constant = -keyboardSize.height + offset;
self.actionBarViewBottomKeyboard.priority = UILayoutPriorityDefaultHigh;
[self.actionBarView.superview layoutIfNeeded];
}];
}
}
- (void)keyboardWillHide:(NSNotification *)notification {
if (self.actionBarView.isHidden) {
self.actionBarViewBottomKeyboard.priority = UILayoutPriorityDefaultLow;
} else {
[UIView animateWithDuration:kDefaultAnimationDuration
animations:^{
self.actionBarViewBottomKeyboard.priority = UILayoutPriorityDefaultLow;
[self.actionBarView.superview layoutIfNeeded];
}];
}
}
@end

View file

@ -1,34 +0,0 @@
#import "MWMAlertViewController.h"
#import "MWMSearchManagerObserver.h"
#import "MWMSearchManagerState.h"
typedef NS_ENUM(NSInteger, MWMSearchManagerRoutingTooltipSearch) {
MWMSearchManagerRoutingTooltipSearchNone,
MWMSearchManagerRoutingTooltipSearchStart,
MWMSearchManagerRoutingTooltipSearchFinish
};
@class SearchTextField;
@interface MWMSearchManager : NSObject
extern const CGFloat kWidthForiPad;
+ (nonnull MWMSearchManager *)manager NS_SWIFT_NAME(manager());
+ (void)addObserver:(nonnull id<MWMSearchManagerObserver>)observer;
+ (void)removeObserver:(nonnull id<MWMSearchManagerObserver>)observer;
@property(nullable, weak, nonatomic) IBOutlet SearchTextField *searchTextField;
@property(nonatomic) MWMSearchManagerState state;
@property(nonatomic) MWMSearchManagerRoutingTooltipSearch routingTooltipSearch;
@property(nonnull, nonatomic) IBOutletCollection(UIView) NSArray *topViews;
- (void)searchText:(nonnull NSString *)text forInputLocale:(nullable NSString *)locale withCategory:(BOOL)isCategory;
#pragma mark - Layout
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(nonnull id<UIViewControllerTransitionCoordinator>)coordinator;
@end

View file

@ -1,538 +0,0 @@
#import "MWMSearchManager.h"
#import "MWMFrameworkListener.h"
#import "MWMMapViewControlsManager.h"
#import "MWMNoMapsViewController.h"
#import "MWMRoutePoint+CPP.h"
#import "MWMRouter.h"
#import "MWMSearchManager+Layout.h"
#import "MWMSearchTableViewController.h"
#import "MapViewController.h"
#import "SearchResult.h"
#import "SwiftBridge.h"
namespace {
typedef NS_ENUM(NSUInteger, MWMSearchManagerActionBarState) {
MWMSearchManagerActionBarStateHidden,
MWMSearchManagerActionBarStateTabBar,
MWMSearchManagerActionBarStateModeFilter
};
using Observer = id<MWMSearchManagerObserver>;
using Observers = NSHashTable<Observer>;
} // namespace
const CGFloat kWidthForiPad = 320;
@interface MWMMapViewControlsManager ()
@property(nonatomic) MWMSearchManager *searchManager;
@end
@interface MWMSearchManager () <MWMSearchTableViewProtocol,
MWMSearchTabViewControllerDelegate,
UITextFieldDelegate,
MWMStorageObserver,
MWMSearchObserver>
@property(weak, nonatomic, readonly) UIViewController *ownerController;
@property(weak, nonatomic, readonly) UIView *searchViewContainer;
@property(weak, nonatomic, readonly) UIView *actionBarContainer;
@property(weak, nonatomic, readonly) MWMMapViewControlsManager *controlsManager;
@property(nonatomic) IBOutlet SearchBar *searchBarView;
@property(weak, nonatomic) IBOutlet SearchActionBarView *actionBarView;
@property(nonatomic) IBOutlet UIView *contentView;
@property(strong, nonatomic) IBOutlet UIView *tableViewContainer;
@property(nonatomic) NSLayoutConstraint *contentViewTopHidden;
@property(nonatomic) NSLayoutConstraint *contentViewBottomHidden;
@property(nonatomic) NSLayoutConstraint *actionBarViewBottomKeyboard;
@property(nonatomic) NSLayoutConstraint *actionBarViewBottomNormal;
@property(nonatomic) UINavigationController *navigationController;
@property(nonatomic) MWMSearchTableViewController *tableViewController;
@property(nonatomic) MWMNoMapsViewController *noMapsController;
@property(nonatomic) Observers *observers;
@end
@implementation MWMSearchManager
+ (MWMSearchManager *)manager {
return [MWMMapViewControlsManager manager].searchManager;
}
- (nullable instancetype)init {
self = [super init];
if (self) {
[NSBundle.mainBundle loadNibNamed:@"MWMSearchView" owner:self options:nil];
self.state = MWMSearchManagerStateHidden;
[MWMSearch addObserver:self];
_observers = [Observers weakObjectsHashTable];
}
return self;
}
- (void)beginSearch {
if (self.state != MWMSearchManagerStateHidden)
self.state = MWMSearchManagerStateTableSearch;
}
- (void)endSearch {
if (self.state != MWMSearchManagerStateHidden)
self.state = MWMSearchManagerStateDefault;
self.searchTextField.text = @"";
[MWMSearch clear];
}
- (void)closeSearch {
[self.searchTextField endEditing:YES];
[self endSearch];
}
#pragma mark - Actions
- (IBAction)textFieldDidEndEditing:(UITextField *)textField {
if (textField.text.length == 0)
[self endSearch];
}
- (IBAction)textFieldTextDidChange:(UITextField *)textField {
NSString *text = textField.text;
if (text.length > 0) {
[self beginSearch];
[MWMSearch searchQuery:text forInputLocale:textField.textInputMode.primaryLanguage withCategory:NO];
} else {
[self endSearch];
}
}
- (IBAction)cancelButtonPressed {
self.state = MWMSearchManagerStateHidden;
}
- (IBAction)backButtonPressed {
self.state = MWMSearchManagerStateTableSearch;
}
#pragma mark - Layout
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[self.navigationController viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
#pragma mark - UITextFieldDelegate
- (void)textFieldDidBeginEditing:(UITextField *)textField {
BOOL const isEmpty = (textField.text.length == 0);
self.state = isEmpty ? MWMSearchManagerStateDefault : MWMSearchManagerStateTableSearch;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
textField.text = [[textField.text stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet]
stringByAppendingString:@" "];
[self textFieldTextDidChange:textField];
[textField resignFirstResponder];
return YES;
}
#pragma mark - MWMSearchTabbedViewProtocol
- (void)searchText:(NSString *)text forInputLocale:(NSString *)locale withCategory:(BOOL)isCategory {
[self beginSearch];
self.searchTextField.text = text;
NSString *inputLocale = locale ?: self.searchTextField.textInputMode.primaryLanguage;
[MWMSearch searchQuery:text forInputLocale:inputLocale withCategory:isCategory];
}
- (void)dismissKeyboard {
[self.searchTextField resignFirstResponder];
}
- (void)processSearchResultAtIndex:(NSInteger)index {
if (self.routingTooltipSearch == MWMSearchManagerRoutingTooltipSearchNone) {
[MWMSearch showResultAtIndex:index];
} else {
BOOL const isStart = self.routingTooltipSearch == MWMSearchManagerRoutingTooltipSearchStart;
SearchResult * result = [MWMSearch resultWithContainerIndex:index];
auto point = [[MWMRoutePoint alloc] initWithCGPoint:result.point
title:result.titleText
subtitle:result.addressText
type:isStart ? MWMRoutePointTypeStart : MWMRoutePointTypeFinish
intermediateIndex:0];
if (isStart)
[MWMRouter buildFromPoint:point bestRouter:NO];
else
[MWMRouter buildToPoint:point bestRouter:NO];
}
if (!IPAD || [MWMNavigationDashboardManager sharedManager].state != MWMNavigationDashboardStateHidden)
self.state = MWMSearchManagerStateResult;
}
#pragma mark - MWMStorageObserver
- (void)processCountryEvent:(NSString *)countryId {
using namespace storage;
NodeStatuses nodeStatuses{};
GetFramework().GetStorage().GetNodeStatuses(countryId.UTF8String, nodeStatuses);
if (nodeStatuses.m_status != NodeStatus::OnDisk)
return;
[self updateTopController];
if (self.state == MWMSearchManagerStateTableSearch || self.state == MWMSearchManagerStateMapSearch) {
NSString *text = self.searchTextField.text;
if (text.length != 0)
[MWMSearch searchQuery:text forInputLocale:self.searchTextField.textInputMode.primaryLanguage withCategory:NO];
}
}
#pragma mark - State changes
- (void)updateTopController {
UIViewController *selfTopVC = self.topController;
if (!selfTopVC || [selfTopVC isEqual:self.navigationController.topViewController])
return;
NSMutableArray *viewControllers = [self.navigationController.viewControllers mutableCopy];
viewControllers[0] = selfTopVC;
[self.navigationController setViewControllers:viewControllers animated:NO];
}
- (void)changeToHiddenState {
self.routingTooltipSearch = MWMSearchManagerRoutingTooltipSearchNone;
[self closeSearch];
MWMMapViewControlsManager *controlsManager = self.controlsManager;
auto const navigationManagerState = [MWMNavigationDashboardManager sharedManager].state;
if (navigationManagerState == MWMNavigationDashboardStateHidden) {
controlsManager.menuState = controlsManager.menuRestoreState;
}
[self viewHidden:YES];
}
- (void)changeToDefaultState {
MWMMapViewControlsManager *controlsManager = self.controlsManager;
[self.navigationController popToRootViewControllerAnimated:NO];
self.searchBarView.state = SearchBarStateReady;
GetFramework().DeactivateMapSelection();
[self animateConstraints:^{
self.contentViewTopHidden.priority = UILayoutPriorityDefaultLow;
self.contentViewBottomHidden.priority = UILayoutPriorityDefaultLow;
}];
auto const navigationManagerState = [MWMNavigationDashboardManager sharedManager].state;
if (navigationManagerState == MWMNavigationDashboardStateHidden) {
controlsManager.menuState = controlsManager.menuRestoreState;
}
[self viewHidden:NO];
self.actionBarState = MWMSearchManagerActionBarStateHidden;
[self.searchTextField becomeFirstResponder];
[self.searchBarView applyTheme];
}
- (void)changeToTableSearchState {
MWMMapViewControlsManager *controlsManager = self.controlsManager;
[self.navigationController popToRootViewControllerAnimated:NO];
self.searchBarView.state = SearchBarStateReady;
GetFramework().DeactivateMapSelection();
[self updateTableSearchActionBar];
auto const navigationManagerState = [MWMNavigationDashboardManager sharedManager].state;
if (navigationManagerState == MWMNavigationDashboardStateHidden) {
controlsManager.menuState = controlsManager.menuRestoreState;
}
[self viewHidden:NO];
[MWMSearch setSearchOnMap:NO];
[self.tableViewController reloadData];
if (![self.navigationController.viewControllers containsObject:self.tableViewController])
[self.navigationController pushViewController:self.tableViewController animated:NO];
}
- (void)changeToMapSearchState {
[self.navigationController popToRootViewControllerAnimated:NO];
self.searchBarView.state = SearchBarStateBack;
self.actionBarState = MWMSearchManagerActionBarStateModeFilter;
if (!IPAD) {
[self animateConstraints:^{
self.contentViewTopHidden.priority = UILayoutPriorityDefaultHigh;
self.contentViewBottomHidden.priority = UILayoutPriorityDefaultHigh;
}];
}
auto const navigationManagerState = [MWMNavigationDashboardManager sharedManager].state;
[self viewHidden:navigationManagerState != MWMNavigationDashboardStateHidden];
self.controlsManager.menuState = MWMBottomMenuStateHidden;
GetFramework().DeactivateMapSelection();
[MWMSearch setSearchOnMap:YES];
[self.tableViewController reloadData];
[self.searchTextField resignFirstResponder];
if (navigationManagerState == MWMNavigationDashboardStateNavigation) {
self.searchTextField.text = @"";
}
}
- (void)changeToResultSearchState {
[self.navigationController popToRootViewControllerAnimated:NO];
self.searchBarView.state = SearchBarStateBack;
self.actionBarState = MWMSearchManagerActionBarStateModeFilter;
if (!IPAD) {
[self animateConstraints:^{
self.contentViewTopHidden.priority = UILayoutPriorityDefaultHigh;
self.contentViewBottomHidden.priority = UILayoutPriorityDefaultHigh;
}];
}
auto const navigationManagerState = [MWMNavigationDashboardManager sharedManager].state;
[self viewHidden:navigationManagerState != MWMNavigationDashboardStateHidden];
[self.tableViewController reloadData];
[self.searchTextField resignFirstResponder];
if (navigationManagerState == MWMNavigationDashboardStateNavigation) {
self.searchTextField.text = @"";
}
}
- (void)animateConstraints:(MWMVoidBlock)block {
UIView *parentView = self.searchViewContainer;
[parentView layoutIfNeeded];
block();
[UIView animateWithDuration:kDefaultAnimationDuration
animations:^{
[parentView layoutIfNeeded];
}];
}
#pragma mark - MWMSearchObserver
- (void)onSearchCompleted {
if (self.state == MWMSearchManagerStateMapSearch || self.state == MWMSearchManagerStateResult) {
self.searchBarView.state = SearchBarStateBack;
} else {
self.searchBarView.state = SearchBarStateReady;
}
if (self.state != MWMSearchManagerStateTableSearch)
return;
[self.tableViewController onSearchCompleted];
[self updateTableSearchActionBar];
}
- (void)onSearchStarted {
self.searchBarView.state = SearchBarStateSearching;
if (self.state != MWMSearchManagerStateTableSearch)
return;
self.actionBarState = MWMSearchManagerActionBarStateModeFilter;
}
- (void)onSearchResultsUpdated {
[self.tableViewController reloadData];
}
- (void)updateTableSearchActionBar {
if (self.state != MWMSearchManagerStateTableSearch)
return;
[self animateConstraints:^{
BOOL hideActionBar = NO;
if ([MWMSearch resultsCount] == 0)
hideActionBar = YES;
else if (IPAD)
hideActionBar = YES;
self.actionBarState =
hideActionBar ? MWMSearchManagerActionBarStateHidden : MWMSearchManagerActionBarStateModeFilter;
self.contentViewTopHidden.priority = UILayoutPriorityDefaultLow;
self.contentViewBottomHidden.priority = UILayoutPriorityDefaultLow;
}];
}
#pragma mark - Add/Remove Observers
+ (void)addObserver:(id<MWMSearchManagerObserver>)observer {
[[MWMSearchManager manager].observers addObject:observer];
}
+ (void)removeObserver:(id<MWMSearchManagerObserver>)observer {
[[MWMSearchManager manager].observers removeObject:observer];
}
#pragma mark - MWMSearchManagerObserver
- (void)onSearchManagerStateChanged {
for (Observer observer in self.observers)
[observer onSearchManagerStateChanged];
}
#pragma mark - Filters
- (IBAction)changeMode {
switch (self.state) {
case MWMSearchManagerStateTableSearch:
self.state = MWMSearchManagerStateMapSearch;
break;
case MWMSearchManagerStateMapSearch:
self.state = MWMSearchManagerStateTableSearch;
break;
default:
break;
}
}
#pragma mark - Properties
- (UINavigationController *)navigationController {
if (!_navigationController) {
_navigationController = [[UINavigationController alloc] init];
[self.contentView addSubview:_navigationController.view];
_navigationController.navigationBarHidden = YES;
}
return _navigationController;
}
- (UIViewController *)topController {
[[MWMStorage sharedStorage] removeObserver:self];
self.noMapsController = nil;
switch (self.state) {
case MWMSearchManagerStateHidden:
return nil;
case MWMSearchManagerStateDefault: {
if (GetFramework().GetStorage().HaveDownloadedCountries()) {
MWMSearchTabViewController *tabViewController = [MWMSearchTabViewController new];
tabViewController.delegate = self;
return tabViewController;
}
self.noMapsController = [MWMNoMapsViewController controller];
[[MWMStorage sharedStorage] addObserver:self];
return self.noMapsController;
}
case MWMSearchManagerStateTableSearch:
return self.tableViewController;
case MWMSearchManagerStateMapSearch:
return self.tableViewController;
case MWMSearchManagerStateResult:
return self.tableViewController;
}
}
- (void)searchTabController:(MWMSearchTabViewController *)viewController
didSearch:(NSString *)didSearch
withCategory:(BOOL)isCategory
{
[self searchText:didSearch forInputLocale:[[AppInfo sharedInfo] languageId] withCategory:isCategory];
}
- (MWMSearchTableViewController *)tableViewController {
if (!_tableViewController)
_tableViewController = [[MWMSearchTableViewController alloc] initWithDelegate:self];
return _tableViewController;
}
- (void)setState:(MWMSearchManagerState)state {
if (_state == state)
return;
_state = state;
[self updateTopController];
switch (state) {
case MWMSearchManagerStateHidden:
[self changeToHiddenState];
break;
case MWMSearchManagerStateDefault:
[self changeToDefaultState];
break;
case MWMSearchManagerStateTableSearch:
[self changeToTableSearchState];
break;
case MWMSearchManagerStateMapSearch:
[self changeToMapSearchState];
break;
case MWMSearchManagerStateResult:
[self changeToResultSearchState];
break;
}
[self onSearchManagerStateChanged];
[self.actionBarView updateForState:state];
[[MapViewController sharedController] updateStatusBarStyle];
}
- (void)viewHidden:(BOOL)hidden {
UIView *searchBarView = self.searchBarView;
UIView *actionBarView = self.actionBarView;
UIView *contentView = self.contentView;
UIView *parentView = self.searchViewContainer;
if (!hidden) {
if (searchBarView.superview) {
[parentView bringSubviewToFront:searchBarView];
[parentView bringSubviewToFront:contentView];
[parentView bringSubviewToFront:actionBarView];
return;
}
[parentView addSubview:searchBarView];
[parentView addSubview:contentView];
[parentView addSubview:actionBarView];
[self layoutTopViews];
// Set Search controller default hidden state for iPad before it will be shown.
if (IPAD) {
self.searchViewContainerLeadingConstraint.constant = -kWidthForiPad;
[parentView.superview layoutIfNeeded];
}
}
[UIView animateWithDuration:kDefaultAnimationDuration
animations:^{
if (IPAD) {
self.searchViewContainerLeadingConstraint.constant = hidden ? -kWidthForiPad : 0;
[parentView.superview layoutIfNeeded];
} else {
CGFloat const alpha = hidden ? 0 : 1;
contentView.alpha = alpha;
actionBarView.alpha = alpha;
searchBarView.alpha = alpha;
}
}
completion:^(BOOL finished) {
if (!hidden)
return;
[contentView removeFromSuperview];
[actionBarView removeFromSuperview];
[searchBarView removeFromSuperview];
[self removeKeyboardObservers];
}];
}
- (void)setActionBarState:(MWMSearchManagerActionBarState)actionBarState {
switch (actionBarState) {
case MWMSearchManagerActionBarStateHidden:
self.actionBarView.hidden = YES;
break;
case MWMSearchManagerActionBarStateTabBar:
self.actionBarView.hidden = YES;
break;
case MWMSearchManagerActionBarStateModeFilter:
self.actionBarView.hidden = NO;
break;
}
}
- (UIViewController *)ownerController {
return [MapViewController sharedController];
}
- (UIView *)searchViewContainer {
return [MapViewController sharedController].searchViewContainer;
}
- (NSLayoutConstraint *)searchViewContainerLeadingConstraint {
return [MapViewController sharedController].searchViewContainerLeadingConstraint;
}
- (UIView *)actionBarContainer {
return [MapViewController sharedController].controlsView;
}
- (MWMMapViewControlsManager *)controlsManager {
return [MWMMapViewControlsManager manager];
}
@end

View file

@ -1,5 +0,0 @@
@protocol MWMSearchManagerObserver<NSObject>
- (void)onSearchManagerStateChanged;
@end

View file

@ -1,8 +0,0 @@
typedef NS_ENUM(NSUInteger, MWMSearchManagerState)
{
MWMSearchManagerStateHidden,
MWMSearchManagerStateDefault,
MWMSearchManagerStateTableSearch,
MWMSearchManagerStateMapSearch,
MWMSearchManagerStateResult
};

View file

@ -1,266 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22155" 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="22131"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MWMSearchManager">
<connections>
<outlet property="actionBarView" destination="d2A-Id-T62" id="UKk-U2-Mwl"/>
<outlet property="contentView" destination="u9y-bx-NGd" id="A03-AI-DbY"/>
<outlet property="searchBarView" destination="g9f-m8-RFv" id="opS-Ll-3Fn"/>
<outlet property="searchTextField" destination="ySb-oA-ZeW" id="Awu-Z4-EdK"/>
<outlet property="tableViewContainer" destination="QQn-4W-Y0s" id="5hb-b3-y8Q"/>
<outletCollection property="topViews" destination="u9y-bx-NGd" id="mAw-CD-QQV"/>
<outletCollection property="topViews" destination="d2A-Id-T62" id="Q0C-Eb-VyX"/>
</connections>
</placeholder>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="g9f-m8-RFv" customClass="SearchBar" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="88"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="C73-2V-To8" userLabel="StatusBarBackground">
<rect key="frame" x="0.0" y="-100" width="320" height="188"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="SearchBarView"/>
</userDefinedRuntimeAttributes>
</view>
<stackView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="DrM-QJ-TCl">
<rect key="frame" x="0.0" y="0.0" width="320" height="88"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ghc-B6-erG">
<rect key="frame" x="0.0" y="0.0" width="320" height="88"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="fill" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" adjustsFontSizeToFit="NO" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="ySb-oA-ZeW" userLabel="Search" customClass="SearchTextField" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="16" y="8" width="232" height="72"/>
<accessibility key="accessibilityConfiguration" identifier="queryField"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" returnKeyType="search"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="SearchSearchTextField"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedPlaceholder" value="search"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="textFieldDidEndEditing:" destination="-1" eventType="editingDidEnd" id="CuD-rP-0bT"/>
<action selector="textFieldTextDidChange:" destination="-1" eventType="editingChanged" id="NU4-jh-Dd4"/>
<outlet property="delegate" destination="-1" id="U2Z-lL-bo1"/>
</connections>
</textField>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="wordWrap" translatesAutoresizingMaskIntoConstraints="NO" id="qkR-cN-NI4">
<rect key="frame" x="248" y="4" width="72" height="80"/>
<accessibility key="accessibilityConfiguration" identifier="cancelButton"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="72" id="8mI-K9-qAu"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<inset key="contentEdgeInsets" minX="4" minY="0.0" maxX="4" maxY="0.0"/>
<state key="normal" title="Cancel">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="cancel"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="whitePrimaryText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="cancelButtonPressed" destination="-1" eventType="touchUpInside" id="cx8-zT-fgZ"/>
</connections>
</button>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_search" translatesAutoresizingMaskIntoConstraints="NO" id="ebE-7b-E9f">
<rect key="frame" x="16" y="8" width="36" height="72"/>
<constraints>
<constraint firstAttribute="width" constant="36" id="8Ta-mE-6dI"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="SearchSearchTextFieldIcon"/>
</userDefinedRuntimeAttributes>
</imageView>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" style="medium" translatesAutoresizingMaskIntoConstraints="NO" id="pnj-0S-fkE">
<rect key="frame" x="16" y="8" width="36" height="72"/>
<constraints>
<constraint firstAttribute="width" constant="36" id="v0W-Aw-eSV"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="SearchSearchTextFieldIcon"/>
</userDefinedRuntimeAttributes>
</activityIndicatorView>
<button opaque="NO" contentMode="center" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CrZ-Cz-xyW">
<rect key="frame" x="16" y="8" width="36" height="72"/>
<constraints>
<constraint firstAttribute="width" constant="36" id="gfO-3m-OFv"/>
</constraints>
<state key="normal" image="ic_search_back"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="SearchSearchTextFieldIcon"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="backButtonPressed" destination="-1" eventType="touchUpInside" id="xyJ-fc-ged"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="pnj-0S-fkE" firstAttribute="top" secondItem="ySb-oA-ZeW" secondAttribute="top" id="4oA-1o-TMf"/>
<constraint firstItem="qkR-cN-NI4" firstAttribute="top" secondItem="ghc-B6-erG" secondAttribute="top" constant="4" id="FlS-h1-5Su"/>
<constraint firstItem="qkR-cN-NI4" firstAttribute="leading" secondItem="ySb-oA-ZeW" secondAttribute="trailing" id="HJ2-r6-yVw"/>
<constraint firstItem="ebE-7b-E9f" firstAttribute="bottom" secondItem="ySb-oA-ZeW" secondAttribute="bottom" id="R45-81-QPe"/>
<constraint firstItem="ebE-7b-E9f" firstAttribute="leading" secondItem="ySb-oA-ZeW" secondAttribute="leading" id="THA-Pu-i5t"/>
<constraint firstItem="CrZ-Cz-xyW" firstAttribute="top" secondItem="ySb-oA-ZeW" secondAttribute="top" id="WxT-Hy-CL1"/>
<constraint firstItem="CrZ-Cz-xyW" firstAttribute="bottom" secondItem="ySb-oA-ZeW" secondAttribute="bottom" id="XIJ-EQ-sfC"/>
<constraint firstAttribute="height" constant="52" id="evQ-q0-G3V"/>
<constraint firstAttribute="bottom" secondItem="qkR-cN-NI4" secondAttribute="bottom" constant="8" id="exK-0S-ML2"/>
<constraint firstAttribute="bottom" secondItem="ySb-oA-ZeW" secondAttribute="bottom" constant="8" id="kc1-xj-dnr"/>
<constraint firstItem="ySb-oA-ZeW" firstAttribute="leading" secondItem="ghc-B6-erG" secondAttribute="leading" constant="16" id="kch-1z-rhZ"/>
<constraint firstItem="CrZ-Cz-xyW" firstAttribute="leading" secondItem="ySb-oA-ZeW" secondAttribute="leading" id="m7Z-jP-Upg"/>
<constraint firstAttribute="trailing" secondItem="qkR-cN-NI4" secondAttribute="trailing" id="ngR-ML-Cnj"/>
<constraint firstItem="pnj-0S-fkE" firstAttribute="leading" secondItem="ySb-oA-ZeW" secondAttribute="leading" id="ove-r5-YFo"/>
<constraint firstItem="qkR-cN-NI4" firstAttribute="centerY" secondItem="ySb-oA-ZeW" secondAttribute="centerY" id="rPK-s8-GJi"/>
<constraint firstItem="pnj-0S-fkE" firstAttribute="bottom" secondItem="ySb-oA-ZeW" secondAttribute="bottom" id="udX-a7-hOd"/>
<constraint firstItem="ySb-oA-ZeW" firstAttribute="top" secondItem="ghc-B6-erG" secondAttribute="top" constant="8" id="yet-nD-qWN"/>
<constraint firstItem="ebE-7b-E9f" firstAttribute="top" secondItem="ySb-oA-ZeW" secondAttribute="top" id="zqb-O1-mO9"/>
</constraints>
</view>
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="VX9-29-j2Z"/>
<color key="backgroundColor" red="0.12549019610000001" green="0.59607843140000005" blue="0.32156862749999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="C73-2V-To8" firstAttribute="top" secondItem="g9f-m8-RFv" secondAttribute="top" constant="-100" id="Alh-jk-902"/>
<constraint firstItem="DrM-QJ-TCl" firstAttribute="leading" secondItem="g9f-m8-RFv" secondAttribute="leading" id="Fqj-iv-GAo"/>
<constraint firstAttribute="bottom" secondItem="DrM-QJ-TCl" secondAttribute="bottom" id="Wtv-8F-BmX"/>
<constraint firstAttribute="bottom" secondItem="C73-2V-To8" secondAttribute="bottom" id="e6z-lu-FFZ"/>
<constraint firstAttribute="trailing" secondItem="DrM-QJ-TCl" secondAttribute="trailing" id="eSZ-Cp-LJl"/>
<constraint firstAttribute="width" secondItem="DrM-QJ-TCl" secondAttribute="width" id="he4-ZG-kIs"/>
<constraint firstItem="C73-2V-To8" firstAttribute="leading" secondItem="g9f-m8-RFv" secondAttribute="leading" id="qhr-if-gM8">
<variation key="heightClass=regular-widthClass=regular" constant="0.0"/>
</constraint>
<constraint firstAttribute="trailing" secondItem="C73-2V-To8" secondAttribute="trailing" id="xV9-by-bNe">
<variation key="heightClass=regular-widthClass=regular" constant="0.0"/>
</constraint>
<constraint firstItem="DrM-QJ-TCl" firstAttribute="top" secondItem="g9f-m8-RFv" secondAttribute="top" id="zwx-cA-Bru"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="SearchBar"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="activityIndicator" destination="pnj-0S-fkE" id="fLT-4I-s5b"/>
<outlet property="backButton" destination="CrZ-Cz-xyW" id="fIz-hG-JFM"/>
<outlet property="searchIcon" destination="ebE-7b-E9f" id="sCN-KI-2a1"/>
<outlet property="searchTextField" destination="ySb-oA-ZeW" id="xHR-vF-csP"/>
<outlet property="stackView" destination="DrM-QJ-TCl" id="Fzm-8G-JnZ"/>
</connections>
<point key="canvasLocation" x="225.59999999999999" y="-211.39430284857573"/>
</view>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="d2A-Id-T62" customClass="SearchActionBarView" customModule="Organic_Maps" customModuleProvider="target" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="361" height="36"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2gF-Kc-0a7">
<rect key="frame" x="0.0" y="0.0" width="361" height="36"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="a8x-P8-Y8p" customClass="MWMButton">
<rect key="frame" x="0.0" y="0.0" width="296" height="36"/>
<accessibility key="accessibilityConfiguration" identifier="viewOnMapButton"/>
<inset key="contentEdgeInsets" minX="10" minY="0.0" maxX="10" maxY="0.0"/>
<state key="normal" title="Map" image="ic_search_mode_map"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="SearchActionBarButton"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="search_show_on_map"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="changeMode" destination="-1" eventType="touchUpInside" id="uAW-Mp-BVp"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Enq-yv-2XZ" customClass="MWMButton">
<rect key="frame" x="296" y="0.0" width="65" height="36"/>
<accessibility key="accessibilityConfiguration" identifier="viewOnMapButton"/>
<inset key="contentEdgeInsets" minX="10" minY="0.0" maxX="10" maxY="0.0"/>
<state key="normal" title="List" image="ic_search_mode_list"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="SearchActionBarButton"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="search_in_table"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="changeMode" destination="-1" eventType="touchUpInside" id="yqV-vF-c3I"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="Bgt-hj-RpB"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="2gF-Kc-0a7" secondAttribute="trailing" id="Fdx-hX-coi"/>
<constraint firstItem="2gF-Kc-0a7" firstAttribute="top" secondItem="d2A-Id-T62" secondAttribute="top" id="Y62-Rf-LAh"/>
<constraint firstAttribute="height" constant="40" id="eLk-al-10m"/>
<constraint firstItem="2gF-Kc-0a7" firstAttribute="leading" secondItem="d2A-Id-T62" secondAttribute="leading" id="itE-d4-hzw"/>
<constraint firstAttribute="bottom" secondItem="2gF-Kc-0a7" secondAttribute="bottom" id="s4H-Jq-xk9"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="SearchActionBarView"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="listButton" destination="Enq-yv-2XZ" id="DUY-wh-ohq"/>
<outlet property="mapButton" destination="a8x-P8-Y8p" id="Rtu-DA-x6c"/>
<outlet property="stackView" destination="2gF-Kc-0a7" id="08D-2t-Cpt"/>
</connections>
<point key="canvasLocation" x="193" y="-58"/>
</view>
<view contentMode="scaleToFill" id="u9y-bx-NGd" userLabel="Content" customClass="MWMSearchContentView">
<rect key="frame" x="0.0" y="0.0" width="320" height="402"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jHn-5d-yXG" customClass="SolidTouchView">
<rect key="frame" x="0.0" y="0.0" width="320" height="402"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QQn-4W-Y0s">
<rect key="frame" x="0.0" y="0.0" width="320" height="368"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="wvK-7O-o3d"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="jHn-5d-yXG" firstAttribute="leading" secondItem="u9y-bx-NGd" secondAttribute="leading" id="1Qk-kN-iT5">
<variation key="heightClass=regular-widthClass=regular" constant="0.0"/>
</constraint>
<constraint firstItem="wvK-7O-o3d" firstAttribute="bottom" secondItem="QQn-4W-Y0s" secondAttribute="bottom" id="5UL-RM-PTF"/>
<constraint firstItem="jHn-5d-yXG" firstAttribute="top" secondItem="u9y-bx-NGd" secondAttribute="top" id="Bgm-2G-pSD"/>
<constraint firstItem="QQn-4W-Y0s" firstAttribute="leading" secondItem="u9y-bx-NGd" secondAttribute="leading" id="CFW-pd-Qj7"/>
<constraint firstItem="QQn-4W-Y0s" firstAttribute="top" secondItem="u9y-bx-NGd" secondAttribute="top" id="T8W-hj-ces"/>
<constraint firstAttribute="trailing" secondItem="jHn-5d-yXG" secondAttribute="trailing" id="aKt-MM-Csi">
<variation key="heightClass=regular-widthClass=regular" constant="0.0"/>
</constraint>
<constraint firstAttribute="bottom" secondItem="jHn-5d-yXG" secondAttribute="bottom" id="hm3-xK-oys">
<variation key="heightClass=regular-widthClass=regular" constant="0.0"/>
</constraint>
<constraint firstAttribute="trailing" secondItem="QQn-4W-Y0s" secondAttribute="trailing" id="pBX-G3-gBz"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<point key="canvasLocation" x="236" y="229"/>
</view>
</objects>
<resources>
<image name="ic_search" width="28" height="28"/>
<image name="ic_search_back" width="24" height="24"/>
<image name="ic_search_mode_list" width="20" height="20"/>
<image name="ic_search_mode_map" width="20" height="20"/>
</resources>
</document>

View file

@ -1,56 +0,0 @@
@objc class SearchActionBarView: SolidTouchView {
@IBOutlet private var mapButton: MWMButton!
@IBOutlet private var listButton: MWMButton!
@IBOutlet private var stackView: UIStackView!
override func awakeFromNib() {
super.awakeFromNib()
hideView(mapButton, isHidden: true, animated: false)
hideView(listButton, isHidden: true, animated: false)
}
@objc func updateForState(_ state: MWMSearchManagerState) {
hideView(mapButton, isHidden: true, animated: false)
hideView(listButton, isHidden: true, animated: false)
iPhoneSpecific {
switch state {
case .tableSearch:
hideView(mapButton, isHidden: false, animated: true)
case .mapSearch:
hideView(listButton, isHidden: false, animated: true)
case .default:
break
case .hidden: fallthrough
case .result: fallthrough
@unknown default:
break
}
}
}
private func hideView(_ view: UIView, isHidden: Bool, animated: Bool) {
view.isHidden = isHidden
if animated {
UIView.animate(withDuration: kDefaultAnimationDuration / 2,
delay: 0,
options: [.beginFromCurrentState],
animations: {
self.layoutIfNeeded()
}, completion: { complete in
if complete {
UIView.animate(withDuration: kDefaultAnimationDuration / 2,
delay: 0, options: [.beginFromCurrentState],
animations: {
view.alpha = isHidden ? 0 : 1
}, completion: nil)
}
})
} else {
view.alpha = isHidden ? 0 : 1
view.isHidden = isHidden
}
}
}

View file

@ -1,57 +0,0 @@
@objc enum SearchBarState: Int {
case ready
case searching
case back
}
final class SearchBar: SolidTouchView {
@IBOutlet private var searchIcon: UIImageView!
@IBOutlet private var activityIndicator: UIActivityIndicatorView!
@IBOutlet private var backButton: UIButton!
@IBOutlet private var searchTextField: SearchTextField!
@IBOutlet private var stackView: UIStackView!
override var visibleAreaAffectDirections: MWMAvailableAreaAffectDirections { return alternative(iPhone: .top, iPad: .left) }
override var placePageAreaAffectDirections: MWMAvailableAreaAffectDirections { return alternative(iPhone: [], iPad: .left) }
override var widgetsAreaAffectDirections: MWMAvailableAreaAffectDirections { return alternative(iPhone: [], iPad: .left) }
override var trafficButtonAreaAffectDirections: MWMAvailableAreaAffectDirections { return alternative(iPhone: .top, iPad: .left) }
override var tabBarAreaAffectDirections: MWMAvailableAreaAffectDirections { return alternative(iPhone: [], iPad: .left) }
override var trackRecordingButtonAreaAffectDirections: MWMAvailableAreaAffectDirections { return alternative(iPhone: .top, iPad: .left) }
@objc var state: SearchBarState = .ready {
didSet {
if state != oldValue {
updateLeftView()
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
updateLeftView()
searchTextField.leftViewMode = UITextField.ViewMode.always
searchTextField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 32, height: 32))
searchTextField.applyTheme()
}
private func updateLeftView() {
searchIcon.isHidden = true
activityIndicator.isHidden = true
backButton.isHidden = true
switch state {
case .ready:
searchIcon.isHidden = false
case .searching:
activityIndicator.isHidden = false
activityIndicator.startAnimating()
case .back:
backButton.isHidden = false
}
}
}

View file

@ -0,0 +1,122 @@
final class PlaceholderView: UIView {
private let activityIndicator: UIActivityIndicatorView?
private let titleLabel = UILabel()
private let subtitleLabel = UILabel()
private let stackView = UIStackView()
private var keyboardHeight: CGFloat = 0
private var centerYConstraint: NSLayoutConstraint!
private var containerModalYTranslation: CGFloat = 0
private let minOffsetFromTheKeyboardTop: CGFloat = 20
private let maxOffsetFromTheTop: CGFloat = 100
init(title: String? = nil, subtitle: String? = nil, hasActivityIndicator: Bool = false) {
self.activityIndicator = hasActivityIndicator ? UIActivityIndicatorView() : nil
super.init(frame: .zero)
setupView(title: title, subtitle: subtitle)
layoutView()
setupKeyboardObservers()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
NotificationCenter.default.removeObserver(self)
}
private func setupKeyboardObservers() {
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillShow(_:)),
name: UIResponder.keyboardWillShowNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillHide(_:)),
name: UIResponder.keyboardWillHideNotification,
object: nil)
}
@objc private func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
keyboardHeight = keyboardFrame.height
reloadConstraints()
}
}
@objc private func keyboardWillHide(_ notification: Notification) {
keyboardHeight = 0
reloadConstraints()
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass {
reloadConstraints()
}
}
private func setupView(title: String?, subtitle: String?) {
if let activityIndicator = activityIndicator {
activityIndicator.hidesWhenStopped = true
activityIndicator.startAnimating()
if #available(iOS 13.0, *) {
activityIndicator.style = .medium
} else {
activityIndicator.style = .gray
}
}
titleLabel.text = title
titleLabel.setFontStyle(.medium16, color: .blackPrimary)
titleLabel.textAlignment = .center
subtitleLabel.text = subtitle
subtitleLabel.setFontStyle(.regular14, color: .blackSecondary)
subtitleLabel.textAlignment = .center
subtitleLabel.isHidden = subtitle == nil
subtitleLabel.numberOfLines = 2
stackView.axis = .vertical
stackView.alignment = .center
stackView.spacing = 8
}
private func layoutView() {
if let activityIndicator = activityIndicator {
stackView.addArrangedSubview(activityIndicator)
}
if let title = titleLabel.text, !title.isEmpty {
stackView.addArrangedSubview(titleLabel)
}
if let subtitle = subtitleLabel.text, !subtitle.isEmpty {
stackView.addArrangedSubview(subtitleLabel)
}
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
centerYConstraint = stackView.centerYAnchor.constraint(equalTo: centerYAnchor)
NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: centerXAnchor),
stackView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor, multiplier: 0.8),
centerYConstraint
])
}
private func reloadConstraints() {
let offset = keyboardHeight > 0 ? max(bounds.height / 2 - keyboardHeight, minOffsetFromTheKeyboardTop + stackView.frame.height) : containerModalYTranslation / 2
let maxOffset = bounds.height / 2 - maxOffsetFromTheTop
centerYConstraint.constant = -min(offset, maxOffset)
layoutIfNeeded()
}
}
// MARK: - ModallyPresentedViewController
extension PlaceholderView: ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat) {
self.containerModalYTranslation = translationY
reloadConstraints()
}
}

View file

@ -0,0 +1,30 @@
/// A transparent view that allows touch events to pass through to the MapViewController's view.
///
/// This view is used to enable interaction with the underlying map while still maintaining a
/// transparent overlay. It does not block touch events but forwards them to the specified `passingView`.
final class MapPassthroughView: UIView {
private weak var passingView: UIView?
init(passingView: UIView) {
self.passingView = passingView
super.init(frame: passingView.bounds)
self.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.alpha = 0
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let passingView else { return nil }
let pointInPassthroughView = passingView.convert(point, from: self)
super.hitTest(point, with: event)
if passingView.bounds.contains(pointInPassthroughView) {
return MapViewController.shared()?.view.hitTest(point, with: event)
}
return nil
}
}

View file

@ -0,0 +1,94 @@
enum ModalScreenPresentationStep {
case fullScreen
case halfScreen
case compact
case hidden
}
extension ModalScreenPresentationStep {
private enum Constants {
static let iPadWidth: CGFloat = 350
static let compactHeightOffset: CGFloat = 120
static let fullScreenHeightFactorPortrait: CGFloat = 0.1
static let halfScreenHeightFactorPortrait: CGFloat = 0.55
static let landscapeTopInset: CGFloat = 10
}
var upper: ModalScreenPresentationStep {
switch self {
case .fullScreen:
return .fullScreen
case .halfScreen:
return .fullScreen
case .compact:
return .halfScreen
case .hidden:
return .compact
}
}
var lower: ModalScreenPresentationStep {
switch self {
case .fullScreen:
return .halfScreen
case .halfScreen:
return .compact
case .compact:
return .compact
case .hidden:
return .hidden
}
}
var first: ModalScreenPresentationStep {
.fullScreen
}
var last: ModalScreenPresentationStep {
.compact
}
func frame(for viewController: UIViewController, in containerView: UIView) -> CGRect {
let isIPad = UIDevice.current.userInterfaceIdiom == .pad
let containerSize = containerView.bounds.size
let safeAreaInsets = containerView.safeAreaInsets
var frame = CGRect(origin: .zero, size: containerSize)
if isIPad {
frame.size.width = Constants.iPadWidth
switch self {
case .hidden:
frame.origin.x = -Constants.iPadWidth
default:
frame.origin.x = .zero
}
return frame
}
let isPortraitOrientation = viewController.traitCollection.verticalSizeClass == .regular
if isPortraitOrientation {
switch self {
case .fullScreen:
frame.origin.y = containerSize.height * Constants.fullScreenHeightFactorPortrait
case .halfScreen:
frame.origin.y = containerSize.height * Constants.halfScreenHeightFactorPortrait
case .compact:
frame.origin.y = containerSize.height - Constants.compactHeightOffset
case .hidden:
frame.origin.y = containerSize.height
}
} else {
frame.size.width = Constants.iPadWidth
frame.origin.x = safeAreaInsets.left
switch self {
case .fullScreen:
frame.origin.y = Constants.landscapeTopInset
case .halfScreen, .compact:
frame.origin.y = containerSize.height - Constants.compactHeightOffset
case .hidden:
frame.origin.y = containerSize.height
}
}
return frame
}
}

View file

@ -0,0 +1,226 @@
protocol ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat)
}
protocol SearchOnMapModalPresentationView: AnyObject {
func setPresentationStep(_ step: ModalScreenPresentationStep)
func close()
}
final class SearchOnMapModalPresentationController: UIPresentationController {
private enum StepChangeAnimation {
case slide
case slideAndBounce
}
private enum Constants {
static let animationDuration: TimeInterval = kDefaultAnimationDuration
static let springDamping: CGFloat = 0.85
static let springVelocity: CGFloat = 0.2
static let iPhoneCornerRadius: CGFloat = 10
static let slowSwipeVelocity: CGFloat = 500
static let fastSwipeDownVelocity: CGFloat = 4000
static let fastSwipeUpVelocity: CGFloat = 3000
static let translationThreshold: CGFloat = 50
static let panGestureThreshold: CGFloat = 5
}
private var initialTranslationY: CGFloat = 0
private weak var interactor: SearchOnMapInteractor? {
(presentedViewController as? SearchOnMapViewController)?.interactor
}
// TODO: replace with set of steps passed from the outside
private var presentationStep: ModalScreenPresentationStep = .fullScreen
private var internalScrollViewContentOffset: CGFloat = 0
private var maxAvailableFrameOfPresentedView: CGRect = .zero
// MARK: - Init
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
iPhoneSpecific {
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
panGestureRecognizer.delegate = self
presentedViewController.view.addGestureRecognizer(panGestureRecognizer)
if let presentedViewController = presentedViewController as? SearchOnMapView {
presentedViewController.scrollViewDelegate = self
}
}
}
// MARK: - Lifecycle
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
presentedView?.frame = frameOfPresentedViewInContainerView
}
override func presentationTransitionWillBegin() {
guard let containerView else { return }
containerView.backgroundColor = .clear
let passThroughView = MapPassthroughView(passingView: containerView)
containerView.addSubview(passThroughView)
}
override func presentationTransitionDidEnd(_ completed: Bool) {
translationYDidUpdate(presentedView?.frame.origin.y ?? 0)
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
super.dismissalTransitionDidEnd(completed)
if completed {
presentedView?.removeFromSuperview()
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateMaxAvailableFrameOfPresentedView()
}
// MARK: - Layout
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView else { return .zero }
let frame = presentationStep.frame(for: presentedViewController, in: containerView)
updateMaxAvailableFrameOfPresentedView()
return frame
}
private func updateMaxAvailableFrameOfPresentedView() {
guard let containerView else { return }
maxAvailableFrameOfPresentedView = ModalScreenPresentationStep.fullScreen.frame(for: presentedViewController, in: containerView)
}
private func updateSideButtonsAvailableArea(_ newY: CGFloat) {
iPhoneSpecific {
guard presentedViewController.traitCollection.verticalSizeClass != .compact else { return }
var sideButtonsAvailableArea = MWMSideButtons.getAvailableArea()
sideButtonsAvailableArea.size.height = newY - sideButtonsAvailableArea.origin.y
MWMSideButtons.updateAvailableArea(sideButtonsAvailableArea)
}
}
// MARK: - Pan gesture handling
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
guard let presentedView, maxAvailableFrameOfPresentedView != .zero else { return }
interactor?.handle(.didStartDraggingSearch)
let translation = gesture.translation(in: presentedView)
let velocity = gesture.velocity(in: presentedView)
switch gesture.state {
case .began:
initialTranslationY = presentedView.frame.origin.y
case .changed:
let newY = max(max(initialTranslationY + translation.y, 0), maxAvailableFrameOfPresentedView.origin.y)
presentedView.frame.origin.y = newY
updateSideButtonsAvailableArea(newY)
translationYDidUpdate(newY)
case .ended:
let nextStep: ModalScreenPresentationStep
if velocity.y > Constants.fastSwipeDownVelocity {
interactor?.handle(.closeSearch)
return
} else if velocity.y < -Constants.fastSwipeUpVelocity {
nextStep = .fullScreen // fast swipe up
} else if velocity.y > Constants.slowSwipeVelocity || translation.y > Constants.translationThreshold {
if presentationStep == .compact {
interactor?.handle(.closeSearch)
return
}
nextStep = presentationStep.lower // regular swipe down
} else if velocity.y < -Constants.slowSwipeVelocity || translation.y < -Constants.translationThreshold {
nextStep = presentationStep.upper // regular swipe up
} else {
// TODO: swipe to closest step on the big translation
nextStep = presentationStep
}
let animation: StepChangeAnimation = abs(velocity.y) > Constants.slowSwipeVelocity ? .slideAndBounce : .slide
animateTo(nextStep, animation: animation)
default:
break
}
}
private func animateTo(_ presentationStep: ModalScreenPresentationStep, animation: StepChangeAnimation = .slide) {
guard let presentedView, let containerView else { return }
self.presentationStep = presentationStep
interactor?.handle(.didUpdatePresentationStep(presentationStep))
let updatedFrame = presentationStep.frame(for: presentedViewController, in: containerView)
let targetYTranslation = updatedFrame.origin.y
switch animation {
case .slide:
UIView.animate(withDuration: Constants.animationDuration,
delay: 0,
options: .curveEaseOut,
animations: { [weak self] in
presentedView.frame = updatedFrame
self?.translationYDidUpdate(targetYTranslation)
self?.updateSideButtonsAvailableArea(targetYTranslation)
})
case .slideAndBounce:
UIView.animate(withDuration: Constants.animationDuration,
delay: 0,
usingSpringWithDamping: Constants.springDamping,
initialSpringVelocity: Constants.springVelocity,
options: .curveLinear,
animations: { [weak self] in
presentedView.frame = updatedFrame
self?.translationYDidUpdate(targetYTranslation)
self?.updateSideButtonsAvailableArea(targetYTranslation)
})
}
}
}
// MARK: - SearchOnMapModalPresentationView
extension SearchOnMapModalPresentationController: SearchOnMapModalPresentationView {
func setPresentationStep(_ step: ModalScreenPresentationStep) {
guard presentationStep != step else { return }
animateTo(step)
}
func close() {
guard let containerView else { return }
updateSideButtonsAvailableArea(containerView.frame.height)
presentedViewController.dismiss(animated: true)
}
}
// MARK: - ModallyPresentedViewController
extension SearchOnMapModalPresentationController: ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat) {
iPhoneSpecific {
(presentedViewController as? SearchOnMapViewController)?.translationYDidUpdate(translationY)
}
}
}
// MARK: - UIGestureRecognizerDelegate
extension SearchOnMapModalPresentationController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// threshold is used to soften transition from the internal scroll zero content offset
internalScrollViewContentOffset < Constants.panGestureThreshold
}
}
// MARK: - SearchOnMapScrollViewDelegate
extension SearchOnMapModalPresentationController: SearchOnMapScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let presentedView else { return }
let hasReachedTheTop = Int(presentedView.frame.origin.y) > Int(maxAvailableFrameOfPresentedView.origin.y)
let hasZeroContentOffset = internalScrollViewContentOffset == 0
if hasReachedTheTop && hasZeroContentOffset {
// prevent the internal scroll view scrolling
scrollView.contentOffset.y = internalScrollViewContentOffset
return
}
internalScrollViewContentOffset = scrollView.contentOffset.y
}
}

View file

@ -0,0 +1,21 @@
@objc
final class SearchOnMapModalTransitionManager: NSObject, UIViewControllerTransitioningDelegate {
weak var presentationController: SearchOnMapModalPresentationView?
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> (any UIViewControllerAnimatedTransitioning)? {
isIPad ? SideMenuPresentationAnimator() : nil
}
func animationController(forDismissed dismissed: UIViewController) -> (any UIViewControllerAnimatedTransitioning)? {
isIPad ? SideMenuDismissalAnimator() : nil
}
func presentationController(forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController) -> UIPresentationController? {
let presentationController = SearchOnMapModalPresentationController(presentedViewController: presented, presenting: presenting)
self.presentationController = presentationController
return presentationController
}
}

View file

@ -0,0 +1,22 @@
final class SideMenuDismissalAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return kDefaultAnimationDuration / 2
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from) else { return }
let initialFrame = transitionContext.initialFrame(for: fromVC)
let targetFrame = initialFrame.offsetBy(dx: -initialFrame.width, dy: 0)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: .zero,
options: .curveEaseIn,
animations: {
fromVC.view.frame = targetFrame
},
completion: {
fromVC.view.removeFromSuperview()
transitionContext.completeTransition($0)
})
}
}

View file

@ -0,0 +1,25 @@
final class SideMenuPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return kDefaultAnimationDuration / 2
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toVC = transitionContext.viewController(forKey: .to) else { return }
let containerView = transitionContext.containerView
let finalFrame = transitionContext.finalFrame(for: toVC)
let originFrame = finalFrame.offsetBy(dx: -finalFrame.width, dy: 0)
containerView.addSubview(toVC.view)
toVC.view.frame = originFrame
toVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: .zero,
options: .curveEaseOut,
animations: {
toVC.view.frame = finalFrame
},
completion: {
transitionContext.completeTransition($0)
})
}
}

View file

@ -0,0 +1,113 @@
protocol SearchOnMapHeaderViewDelegate: UISearchBarDelegate {
func cancelButtonDidTap()
}
final class SearchOnMapHeaderView: UIView {
weak var delegate: SearchOnMapHeaderViewDelegate? {
didSet {
searchBar.delegate = delegate
}
}
private enum Constants {
static let grabberHeight: CGFloat = 5
static let grabberWidth: CGFloat = 36
static let grabberTopMargin: CGFloat = 5
static let cancelButtonInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 8)
}
private let grabberView = UIView()
private let searchBar = UISearchBar()
private let cancelButton = UIButton()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
layoutView()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
setStyle(.searchHeader)
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
setupGrabberView()
setupSearchBar()
setupCancelButton()
}
private func setupGrabberView() {
grabberView.setStyle(.background)
grabberView.layer.setCorner(radius: Constants.grabberHeight / 2)
iPadSpecific { [weak self] in
self?.grabberView.isHidden = true
}
}
private func setupSearchBar() {
searchBar.placeholder = L("search")
searchBar.showsCancelButton = false
if #available(iOS 13.0, *) {
searchBar.searchTextField.clearButtonMode = .always
searchBar.returnKeyType = .search
searchBar.searchTextField.enablesReturnKeyAutomatically = true
}
}
private func setupCancelButton() {
cancelButton.tintColor = .whitePrimaryText()
cancelButton.setStyle(.clearBackground)
cancelButton.setTitle(L("cancel"), for: .normal)
cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside)
}
private func layoutView() {
addSubview(grabberView)
addSubview(searchBar)
addSubview(cancelButton)
grabberView.translatesAutoresizingMaskIntoConstraints = false
searchBar.translatesAutoresizingMaskIntoConstraints = false
cancelButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
grabberView.topAnchor.constraint(equalTo: topAnchor, constant: Constants.grabberTopMargin),
grabberView.centerXAnchor.constraint(equalTo: centerXAnchor),
grabberView.widthAnchor.constraint(equalToConstant: Constants.grabberWidth),
grabberView.heightAnchor.constraint(equalToConstant: Constants.grabberHeight),
searchBar.topAnchor.constraint(equalTo: grabberView.bottomAnchor),
searchBar.leadingAnchor.constraint(equalTo: leadingAnchor),
searchBar.trailingAnchor.constraint(equalTo: cancelButton.leadingAnchor, constant: -Constants.cancelButtonInsets.left),
cancelButton.centerYAnchor.constraint(equalTo: searchBar.centerYAnchor),
cancelButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Constants.cancelButtonInsets.right),
bottomAnchor.constraint(equalTo: searchBar.bottomAnchor)
])
}
@objc private func cancelButtonTapped() {
delegate?.cancelButtonDidTap()
}
func setSearchText(_ text: String) {
searchBar.text = text
}
func setIsSearching(_ isSearching: Bool) {
if isSearching {
searchBar.becomeFirstResponder()
} else if searchBar.isFirstResponder {
searchBar.resignFirstResponder()
}
}
var searchText: SearchOnMap.SearchText {
SearchOnMap.SearchText(searchBar.text ?? "", locale: searchBar.textInputMode?.primaryLanguage)
}
}

View file

@ -0,0 +1,165 @@
final class SearchOnMapInteractor: NSObject {
private let presenter: SearchOnMapPresenter
private let searchManager: SearchManager.Type
private let routeManager: MWMRouter.Type
private var isUpdatesDisabled = false
private var showResultsOnMap: Bool = false
var routingTooltipSearch: SearchOnMapRoutingTooltipSearch = .none
init(presenter: SearchOnMapPresenter,
searchManager: SearchManager.Type = Search.self,
routeManager: MWMRouter.Type = MWMRouter.self) {
self.presenter = presenter
self.searchManager = searchManager
self.routeManager = routeManager
super.init()
searchManager.add(self)
}
deinit {
searchManager.remove(self)
}
func handle(_ event: SearchOnMap.Request) {
let response = resolve(event)
presenter.process(response)
}
private func resolve(_ event: SearchOnMap.Request) -> SearchOnMap.Response {
switch event {
case .openSearch:
return .showHistoryAndCategory
case .hideSearch:
return .setSearchScreenHidden(true)
case .didStartDraggingSearch:
return .setIsTyping(false)
case .didStartTyping:
return .setIsTyping(true)
case .didType(let searchText):
return processTypedText(searchText)
case .clearButtonDidTap:
return processClearButtonDidTap()
case .didSelectText(let searchText, let isCategory):
return processSelectedText(searchText, isCategory: isCategory)
case .searchButtonDidTap(let searchText):
return processSearchButtonDidTap(searchText)
case .didSelectResult(let result, let index, let searchText):
return processSelectedResult(result, index: index, searchText: searchText)
case .didSelectPlaceOnMap:
return isIPad ? .none : .setSearchScreenHidden(true)
case .didDeselectPlaceOnMap:
return deselectPlaceOnMap()
case .didStartDraggingMap:
return .setSearchScreenCompact
case .didUpdatePresentationStep(let step):
return .updatePresentationStep(step)
case .closeSearch:
return closeSearch()
}
}
private func processClearButtonDidTap() -> SearchOnMap.Response {
isUpdatesDisabled = true
searchManager.clear()
return .clearSearch
}
private func processSearchButtonDidTap(_ searchText: SearchOnMap.SearchText) -> SearchOnMap.Response {
searchManager.saveQuery(searchText.text,
forInputLocale: searchText.locale)
showResultsOnMap = true
return .showOnTheMap
}
private func processTypedText(_ searchText: SearchOnMap.SearchText) -> SearchOnMap.Response {
isUpdatesDisabled = false
showResultsOnMap = true
searchManager.searchQuery(searchText.text,
forInputLocale: searchText.locale,
withCategory: false)
return .startSearching
}
private func processSelectedText(_ searchText: SearchOnMap.SearchText, isCategory: Bool) -> SearchOnMap.Response {
isUpdatesDisabled = false
searchManager.saveQuery(searchText.text,
forInputLocale: searchText.locale)
searchManager.searchQuery(searchText.text,
forInputLocale: searchText.locale,
withCategory: isCategory)
showResultsOnMap = true
return .selectText(searchText.text)
}
private func processSelectedResult(_ result: SearchResult, index: Int, searchText: SearchOnMap.SearchText) -> SearchOnMap.Response {
switch result.itemType {
case .regular:
searchManager.saveQuery(searchText.text,
forInputLocale:searchText.locale)
switch routingTooltipSearch {
case .none:
searchManager.showResult(at: UInt(index))
case .start:
let point = MWMRoutePoint(cgPoint: result.point,
title: result.titleText,
subtitle: result.addressText,
type: .start,
intermediateIndex: 0)
routeManager.build(from: point, bestRouter: false)
case .finish:
let point = MWMRoutePoint(cgPoint: result.point,
title: result.titleText,
subtitle: result.addressText,
type: .finish,
intermediateIndex: 0)
routeManager.build(to: point, bestRouter: false)
@unknown default:
fatalError("Unsupported routingTooltipSearch")
}
return isIPad ? .none : .setSearchScreenHidden(true)
case .suggestion:
searchManager.searchQuery(result.suggestion,
forInputLocale: searchText.locale,
withCategory: result.isPureSuggest)
return .selectText(result.suggestion)
@unknown default:
fatalError("Unsupported result type")
}
}
private func deselectPlaceOnMap() -> SearchOnMap.Response {
routingTooltipSearch = .none
searchManager.showViewportSearchResultsOnMap()
return .setSearchScreenHidden(false)
}
private func closeSearch() -> SearchOnMap.Response {
routingTooltipSearch = .none
isUpdatesDisabled = true
showResultsOnMap = false
searchManager.clear()
return .close
}
}
// MARK: - MWMSearchObserver
extension SearchOnMapInteractor: MWMSearchObserver {
func onSearchCompleted() {
guard !isUpdatesDisabled else { return }
let results = searchManager.getResults()
if showResultsOnMap && !results.isEmpty {
searchManager.showEverywhereSearchResultsOnMap()
showResultsOnMap = false
}
presenter.process(.showResults(SearchOnMap.SearchResults(results), isSearchCompleted: true))
}
func onSearchResultsUpdated() {
guard !isUpdatesDisabled else { return }
let results = searchManager.getResults()
guard !results.isEmpty else { return }
presenter.process(.showResults(SearchOnMap.SearchResults(results), isSearchCompleted: false))
}
}

View file

@ -0,0 +1,96 @@
@objc
enum SearchOnMapState: Int {
case searching
case hidden
case closed
}
@objc
enum SearchOnMapRoutingTooltipSearch: Int {
case none
case start
case finish
}
@objc
protocol SearchOnMapManagerObserver: AnyObject {
func searchManager(didChangeState state: SearchOnMapState)
}
@objcMembers
final class SearchOnMapManager: NSObject {
private let navigationController: UINavigationController
private weak var interactor: SearchOnMapInteractor?
private let observers = ListenerContainer<SearchOnMapManagerObserver>()
// MARK: - Public properties
weak var viewController: UIViewController?
var isSearching: Bool { viewController != nil }
init(navigationController: UINavigationController = MapViewController.shared()!.navigationController!) {
self.navigationController = navigationController
}
// MARK: - Public methods
func startSearching(isRouting: Bool) {
if viewController != nil {
interactor?.handle(.openSearch)
return
}
FrameworkHelper.deactivateMapSelection()
let viewController = buildViewController(isRouting: isRouting)
self.viewController = viewController
self.interactor = viewController.interactor
navigationController.present(viewController, animated: true)
}
func hide() {
interactor?.handle(.hideSearch)
}
func close() {
interactor?.handle(.closeSearch)
}
func setRoutingTooltip(_ tooltip: SearchOnMapRoutingTooltipSearch) {
interactor?.routingTooltipSearch = tooltip
}
func setPlaceOnMapSelected(_ isSelected: Bool) {
interactor?.handle(isSelected ? .didSelectPlaceOnMap : .didDeselectPlaceOnMap)
}
func setMapIsDragging() {
interactor?.handle(.didStartDraggingMap)
}
func searchText(_ text: String, locale: String, isCategory: Bool) {
let searchText = SearchOnMap.SearchText(text, locale: locale)
interactor?.handle(.didSelectText(searchText, isCategory: isCategory))
}
func addObserver(_ observer: SearchOnMapManagerObserver) {
observers.addListener(observer)
}
func removeObserver(_ observer: SearchOnMapManagerObserver) {
observers.removeListener(observer)
}
// MARK: - Private methods
private func buildViewController(isRouting: Bool) -> SearchOnMapViewController {
let transitioningManager = SearchOnMapModalTransitionManager()
let presenter = SearchOnMapPresenter(transitionManager: transitioningManager,
isRouting: isRouting,
didChangeState: { [weak self] state in
guard let self else { return }
self.observers.forEach { observer in observer.searchManager(didChangeState: state) }
})
let interactor = SearchOnMapInteractor(presenter: presenter)
let viewController = SearchOnMapViewController(interactor: interactor)
presenter.view = viewController
viewController.modalPresentationStyle = .custom
viewController.transitioningDelegate = transitioningManager
return viewController
}
}

View file

@ -0,0 +1,94 @@
enum SearchOnMap {
struct ViewModel: Equatable {
enum ContentState: Equatable {
case historyAndCategory
case results(SearchResults)
case noResults
case searching
}
var isTyping: Bool
var skipSuggestions: Bool
var searchingText: String?
var contentState: ContentState
var presentationStep: ModalScreenPresentationStep
}
struct SearchResults: Equatable {
let results: [SearchResult]
let hasPartialMatch: Bool
let isEmpty: Bool
let count: Int
let suggestionsCount: Int
init(_ results: [SearchResult]) {
self.results = results
self.hasPartialMatch = !results.allSatisfy { $0.highlightRanges.isEmpty }
self.isEmpty = results.isEmpty
self.count = results.count
self.suggestionsCount = results.filter { $0.itemType == .suggestion }.count
}
}
struct SearchText {
let text: String
let locale: String
init(_ text: String, locale: String? = nil) {
self.text = text
self.locale = locale ?? AppInfo.shared().languageId
}
}
enum Request {
case openSearch
case hideSearch
case closeSearch
case didStartDraggingSearch
case didStartDraggingMap
case didStartTyping
case didType(SearchText)
case didSelectText(SearchText, isCategory: Bool)
case didSelectResult(SearchResult, atIndex: Int, withSearchText: SearchText)
case searchButtonDidTap(SearchText)
case clearButtonDidTap
case didSelectPlaceOnMap
case didDeselectPlaceOnMap
case didUpdatePresentationStep(ModalScreenPresentationStep)
}
enum Response: Equatable {
case startSearching
case showOnTheMap
case setIsTyping(Bool)
case showHistoryAndCategory
case showResults(SearchResults, isSearchCompleted: Bool = false)
case selectText(String?)
case clearSearch
case setSearchScreenHidden(Bool)
case setSearchScreenCompact
case updatePresentationStep(ModalScreenPresentationStep)
case close
case none
}
}
extension SearchOnMap.SearchResults {
static let empty = SearchOnMap.SearchResults([])
subscript(index: Int) -> SearchResult {
results[index]
}
mutating func skipSuggestions() {
self = SearchOnMap.SearchResults(results.filter { $0.itemType != .suggestion })
}
}
extension SearchOnMap.ViewModel {
static let initial = SearchOnMap.ViewModel(isTyping: false,
skipSuggestions: false,
searchingText: nil,
contentState: .historyAndCategory,
presentationStep: .fullScreen)
}

View file

@ -0,0 +1,117 @@
final class SearchOnMapPresenter {
typealias Response = SearchOnMap.Response
typealias ViewModel = SearchOnMap.ViewModel
weak var view: SearchOnMapView?
weak var presentationView: SearchOnMapModalPresentationView? { transitionManager.presentationController }
private var searchState: SearchOnMapState = .searching {
didSet {
guard searchState != oldValue else { return }
didChangeState?(searchState)
}
}
private let transitionManager: SearchOnMapModalTransitionManager
private var viewModel: ViewModel = .initial
private var isRouting: Bool
private var didChangeState: ((SearchOnMapState) -> Void)?
init(transitionManager: SearchOnMapModalTransitionManager, isRouting: Bool, didChangeState: ((SearchOnMapState) -> Void)?) {
self.transitionManager = transitionManager
self.isRouting = isRouting
self.didChangeState = didChangeState
didChangeState?(searchState)
}
func process(_ response: SearchOnMap.Response) {
guard response != .none else { return }
if response == .close {
searchState = .closed
presentationView?.close()
return
}
let showSearch = response == .setSearchScreenHidden(false) || response == .showHistoryAndCategory
guard viewModel.presentationStep != .hidden || showSearch else {
return
}
let newViewModel = resolve(action: response, with: viewModel)
if viewModel != newViewModel {
viewModel = newViewModel
view?.render(newViewModel)
searchState = newViewModel.presentationStep.searchState
presentationView?.setPresentationStep(newViewModel.presentationStep)
}
}
private func resolve(action: Response, with previousViewModel: ViewModel) -> ViewModel {
var viewModel = previousViewModel
viewModel.searchingText = nil // should not be nil only when the text is passed to the search field
switch action {
case .startSearching:
viewModel.isTyping = true
viewModel.skipSuggestions = false
viewModel.contentState = .searching
case .showOnTheMap:
viewModel.isTyping = false
viewModel.skipSuggestions = true
viewModel.presentationStep = isRouting ? .hidden : .halfScreen
if case .results(var results) = viewModel.contentState, !results.isEmpty {
results.skipSuggestions()
viewModel.contentState = .results(results)
}
case .setIsTyping(let isSearching):
viewModel.isTyping = isSearching
if isSearching {
viewModel.presentationStep = .fullScreen
}
case .showHistoryAndCategory:
viewModel.isTyping = true
viewModel.contentState = .historyAndCategory
viewModel.presentationStep = .fullScreen
case .showResults(var searchResults, let isSearchCompleted):
if (viewModel.skipSuggestions) {
searchResults.skipSuggestions()
}
viewModel.contentState = searchResults.isEmpty && isSearchCompleted ? .noResults : .results(searchResults)
case .selectText(let text):
viewModel.isTyping = false
viewModel.skipSuggestions = false
viewModel.searchingText = text
viewModel.contentState = .searching
viewModel.presentationStep = isRouting ? .hidden : .halfScreen
case .clearSearch:
viewModel.searchingText = ""
viewModel.isTyping = true
viewModel.skipSuggestions = false
viewModel.contentState = .historyAndCategory
viewModel.presentationStep = .fullScreen
case .setSearchScreenHidden(let isHidden):
viewModel.isTyping = false
viewModel.presentationStep = isHidden ? .hidden : (isRouting ? .fullScreen : .halfScreen)
case .setSearchScreenCompact:
viewModel.isTyping = false
viewModel.presentationStep = .compact
case .updatePresentationStep(let step):
viewModel.presentationStep = step
case .close, .none:
break
}
return viewModel
}
}
private extension ModalScreenPresentationStep {
var searchState: SearchOnMapState {
switch self {
case .fullScreen, .halfScreen, .compact:
return .searching
case .hidden:
return .hidden
}
}
}

View file

@ -0,0 +1,362 @@
protocol SearchOnMapView: AnyObject {
var scrollViewDelegate: SearchOnMapScrollViewDelegate? { get set }
func render(_ viewModel: SearchOnMap.ViewModel)
}
@objc
protocol SearchOnMapScrollViewDelegate: AnyObject {
func scrollViewDidScroll(_ scrollView: UIScrollView)
}
final class SearchOnMapViewController: UIViewController {
typealias ViewModel = SearchOnMap.ViewModel
typealias ContentState = SearchOnMap.ViewModel.ContentState
typealias SearchText = SearchOnMap.SearchText
fileprivate enum Constants {
static let categoriesHeight: CGFloat = 100
static let filtersHeight: CGFloat = 50
static let keyboardAnimationDuration: CGFloat = 0.3
static let cancelButtonInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 8)
static let estimatedRowHeight: CGFloat = 80
}
let interactor: SearchOnMapInteractor
weak var scrollViewDelegate: SearchOnMapScrollViewDelegate?
private var searchResults = SearchOnMap.SearchResults([])
// MARK: - UI Elements
private let headerView = SearchOnMapHeaderView()
private let containerView = UIView()
private let resultsTableView = UITableView()
private let historyAndCategoryTabViewController = SearchTabViewController()
// TODO: implement filters
private let filtersCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
return UICollectionView(frame: .zero, collectionViewLayout: layout)
}()
private var searchingActivityView = PlaceholderView(hasActivityIndicator: true)
private var containerModalYTranslation: CGFloat = 0
private var searchNoResultsView = PlaceholderView(title: L("search_not_found"),
subtitle: L("search_not_found_query"))
// MARK: - Init
init(interactor: SearchOnMapInteractor) {
self.interactor = interactor
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
layoutViews()
interactor.handle(.openSearch)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
headerView.setIsSearching(false)
}
// MARK: - Private methods
private func setupViews() {
view.setStyle(.background)
setupTapGestureRecognizer()
setupHeaderView()
setupContainerView()
setupResultsTableView()
setupHistoryAndCategoryTabView()
setupResultsTableView()
setupFiltersCollectionView()
}
private func setupTapGestureRecognizer() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapOutside))
tapGesture.cancelsTouchesInView = false
view.addGestureRecognizer(tapGesture)
}
private func setupHeaderView() {
headerView.delegate = self
}
private func setupContainerView() {
containerView.setStyle(.background)
}
private func setupResultsTableView() {
resultsTableView.setStyle(.background)
resultsTableView.estimatedRowHeight = Constants.estimatedRowHeight
resultsTableView.rowHeight = UITableView.automaticDimension
resultsTableView.registerNib(cellClass: SearchSuggestionCell.self)
resultsTableView.registerNib(cellClass: SearchCommonCell.self)
resultsTableView.dataSource = self
resultsTableView.delegate = self
resultsTableView.keyboardDismissMode = .onDrag
}
private func setupHistoryAndCategoryTabView() {
historyAndCategoryTabViewController.delegate = self
}
// TODO: (KK) Implement filters collection viewe
private func setupFiltersCollectionView() {
filtersCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "FilterCell")
filtersCollectionView.dataSource = self
}
private func layoutViews() {
view.addSubview(headerView)
view.addSubview(containerView)
headerView.translatesAutoresizingMaskIntoConstraints = false
containerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
headerView.topAnchor.constraint(equalTo: view.topAnchor),
headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
containerView.topAnchor.constraint(equalTo: headerView.bottomAnchor),
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
layoutResultsView()
layoutHistoryAndCategoryTabView()
layoutSearchNoResultsView()
layoutSearchingView()
}
private func layoutResultsView() {
containerView.addSubview(resultsTableView)
resultsTableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
resultsTableView.topAnchor.constraint(equalTo: containerView.topAnchor),
resultsTableView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
resultsTableView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
resultsTableView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
}
private func layoutHistoryAndCategoryTabView() {
containerView.addSubview(historyAndCategoryTabViewController.view)
historyAndCategoryTabViewController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
historyAndCategoryTabViewController.view.topAnchor.constraint(equalTo: containerView.topAnchor),
historyAndCategoryTabViewController.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
historyAndCategoryTabViewController.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
historyAndCategoryTabViewController.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
}
private func layoutSearchNoResultsView() {
searchNoResultsView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(searchNoResultsView)
NSLayoutConstraint.activate([
searchNoResultsView.topAnchor.constraint(equalTo: containerView.topAnchor),
searchNoResultsView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
searchNoResultsView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
searchNoResultsView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
}
private func layoutSearchingView() {
containerView.insertSubview(searchingActivityView, at: 0)
searchingActivityView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
searchingActivityView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
searchingActivityView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
searchingActivityView.topAnchor.constraint(equalTo: containerView.topAnchor),
searchingActivityView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
}
// MARK: - Handle Button Actions
@objc private func handleTapOutside(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: view)
if resultsTableView.frame.contains(location) && searchResults.isEmpty {
headerView.setIsSearching(false)
}
}
// MARK: - Handle State Updates
private func setContent(_ content: ContentState) {
switch content {
case .historyAndCategory:
historyAndCategoryTabViewController.reloadSearchHistory()
case let .results(results):
if searchResults != results {
searchResults = results
resultsTableView.reloadData()
}
case .noResults:
searchResults = .empty
resultsTableView.reloadData()
case .searching:
break
}
showView(viewToShow(for: content))
}
private func viewToShow(for content: ContentState) -> UIView {
switch content {
case .historyAndCategory:
return historyAndCategoryTabViewController.view
case .results:
return resultsTableView
case .noResults:
return searchNoResultsView
case .searching:
return searchingActivityView
}
}
private func showView(_ view: UIView) {
let viewsToHide: [UIView] = [resultsTableView,
historyAndCategoryTabViewController.view,
searchNoResultsView,
searchingActivityView].filter { $0 != view }
UIView.transition(with: containerView,
duration: kDefaultAnimationDuration / 2,
options: [.transitionCrossDissolve, .curveEaseInOut], animations: {
viewsToHide.forEach { viewToHide in
view.isHidden = false
view.alpha = 1
viewToHide.isHidden = true
viewToHide.alpha = 0
}
})
}
private func setIsSearching(_ isSearching: Bool) {
headerView.setIsSearching(isSearching)
}
private func replaceSearchText(with text: String) {
headerView.setSearchText(text)
}
}
// MARK: - Public methods
extension SearchOnMapViewController: SearchOnMapView {
func render(_ viewModel: ViewModel) {
setContent(viewModel.contentState)
setIsSearching(viewModel.isTyping)
if let searchingText = viewModel.searchingText {
replaceSearchText(with: searchingText)
}
}
}
// MARK: - ModallyPresentedViewController
extension SearchOnMapViewController: ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat) {
self.containerModalYTranslation = translationY
resultsTableView.contentInset.bottom = translationY
historyAndCategoryTabViewController.translationYDidUpdate(translationY)
searchNoResultsView.translationYDidUpdate(translationY)
searchingActivityView.translationYDidUpdate(translationY)
}
}
// MARK: - UITableViewDataSource
extension SearchOnMapViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
searchResults.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let result = searchResults[indexPath.row]
switch result.itemType {
case .regular:
let cell = tableView.dequeueReusableCell(cell: SearchCommonCell.self, indexPath: indexPath)
cell.configure(with: result, isPartialMatching: searchResults.hasPartialMatch)
return cell
case .suggestion:
let cell = tableView.dequeueReusableCell(cell: SearchSuggestionCell.self, indexPath: indexPath)
cell.configure(with: result, isPartialMatching: true)
cell.isLastCell = indexPath.row == searchResults.suggestionsCount - 1
return cell
@unknown default:
fatalError("Unknown item type")
}
}
}
// MARK: - UITableViewDelegate
extension SearchOnMapViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let result = searchResults[indexPath.row]
interactor.handle(.didSelectResult(result, atIndex: indexPath.row, withSearchText: headerView.searchText))
tableView.deselectRow(at: indexPath, animated: true)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
interactor.handle(.didStartDraggingSearch)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollViewDelegate?.scrollViewDidScroll(scrollView)
}
}
// MARK: - UICollectionViewDataSource
extension SearchOnMapViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// TODO: remove search from here
Int(Search.resultsCount())
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FilterCell", for: indexPath)
return cell
}
}
// MARK: - SearchOnMapHeaderViewDelegate
extension SearchOnMapViewController: SearchOnMapHeaderViewDelegate {
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
interactor.handle(.didStartTyping)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
guard !searchText.isEmpty else {
interactor.handle(.clearButtonDidTap)
return
}
interactor.handle(.didType(SearchText(searchText, locale: searchBar.textInputMode?.primaryLanguage)))
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let searchText = searchBar.text, !searchText.isEmpty else { return }
interactor.handle(.searchButtonDidTap(SearchText(searchText, locale: searchBar.textInputMode?.primaryLanguage)))
}
func cancelButtonDidTap() {
interactor.handle(.closeSearch)
}
}
// MARK: - SearchTabViewControllerDelegate
extension SearchOnMapViewController: SearchTabViewControllerDelegate {
func searchTabController(_ viewController: SearchTabViewController, didSearch text: String, withCategory: Bool) {
interactor.handle(.didSelectText(SearchText(text, locale: nil), isCategory: withCategory))
}
}

View file

@ -1,31 +0,0 @@
class SearchTextField: UITextField {
override func drawPlaceholder(in rect: CGRect) {
guard let font = font, let tint = tintColor else {
super.drawPlaceholder(in: rect);
return
}
placeholder?.draw(
in: rect,
withAttributes: [
NSAttributedString.Key.font: font,
NSAttributedString.Key.foregroundColor: tint
])
}
override func layoutSubviews() {
super.layoutSubviews()
for view in subviews {
if (view is UIButton) {
let button = view as? UIButton
let clearButtonImage: UIImage?
if #available(iOS 13.0, *) {
clearButtonImage = UIImage(named: "ic_clear")?.withRenderingMode(.alwaysTemplate).withTintColor(tintColor)
} else {
clearButtonImage = UIImage(named: "ic_search_clear_14")
}
button?.setImage(clearButtonImage, for: .normal)
button?.tintColor = tintColor
}
}
}
}

View file

@ -1,5 +0,0 @@
@interface MWMSearchTableView : UIView
- (void)hideNoResultsView:(BOOL)hide;
@end

View file

@ -1,71 +0,0 @@
#import "MWMSearchTableView.h"
#import "MWMKeyboard.h"
#import "MWMSearchNoResults.h"
@interface MWMSearchTableView ()<MWMKeyboardObserver>
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * noResultsBottomOffset;
@property(weak, nonatomic) IBOutlet UIView * noResultsContainer;
@property(weak, nonatomic) IBOutlet UIView * noResultsWrapper;
@property(nonatomic) MWMSearchNoResults * noResultsView;
@end
@implementation MWMSearchTableView
- (void)awakeFromNib
{
[super awakeFromNib];
CALayer * sl = self.layer;
sl.shouldRasterize = YES;
sl.rasterizationScale = UIScreen.mainScreen.scale;
[MWMKeyboard addObserver:self];
}
- (void)hideNoResultsView:(BOOL)hide
{
if (hide)
{
self.noResultsContainer.hidden = YES;
[self.noResultsView removeFromSuperview];
}
else
{
self.noResultsContainer.hidden = NO;
[self.noResultsWrapper addSubview:self.noResultsView];
[self onKeyboardAnimation];
}
}
#pragma mark - MWMKeyboard
- (void)onKeyboardAnimation
{
CGFloat const keyboardHeight = [MWMKeyboard keyboardHeight];
if (keyboardHeight >= self.height)
return;
self.noResultsBottomOffset.constant = keyboardHeight;
if (self.superview)
[self layoutIfNeeded];
}
- (void)onKeyboardWillAnimate
{
if (self.superview)
[self layoutIfNeeded];
}
- (MWMSearchNoResults *)noResultsView
{
if (!_noResultsView)
{
_noResultsView = [MWMSearchNoResults viewWithImage:nil
title:L(@"search_not_found")
text:L(@"search_not_found_query")];
}
return _noResultsView;
}
@end

View file

@ -1,27 +0,0 @@
#import "MWMSearch.h"
#import "MWMSearchManager.h"
#import "MWMSearchTabbedViewProtocol.h"
#import "MWMViewController.h"
#include <CoreApi/Framework.h>
@class SearchTextField;
@protocol MWMSearchTableViewProtocol<MWMSearchTabbedViewProtocol>
@property(nullable, weak, nonatomic) SearchTextField * searchTextField;
@property(nonatomic) MWMSearchManagerState state;
- (void)processSearchResultAtIndex:(NSInteger)index;
@end
@interface MWMSearchTableViewController : MWMViewController<MWMSearchObserver>
- (nonnull instancetype)init __attribute__((unavailable("init is not available")));
- (nonnull instancetype)initWithDelegate:(nonnull id<MWMSearchTableViewProtocol>)delegate;
- (void)reloadData;
@end

View file

@ -1,143 +0,0 @@
#import "MWMSearchTableViewController.h"
#import "MWMSearchCommonCell.h"
#import "MWMSearchSuggestionCell.h"
#import "MWMSearchTableView.h"
#import "SearchResult.h"
#import "SwiftBridge.h"
#include "platform/localization.hpp"
#include "search/result.hpp"
namespace {
NSString *GetLocalizedTypeName(search::Result const &result) {
return @(result.GetLocalizedFeatureType().c_str());
}
}
@interface MWMSearchTableViewController () <UITableViewDataSource, UITableViewDelegate>
@property(weak, nonatomic) IBOutlet UITableView *tableView;
@property(weak, nonatomic) id<MWMSearchTableViewProtocol> delegate;
@end
@implementation MWMSearchTableViewController
- (nonnull instancetype)initWithDelegate:(id<MWMSearchTableViewProtocol>)delegate {
self = [super init];
if (self)
_delegate = delegate;
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self setupTableView];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.tableView.hidden = NO;
self.tableView.insetsContentViewsToSafeArea = YES;
[(MWMSearchTableView *)self.view hideNoResultsView:YES];
[self reloadData];
}
- (void)setupTableView {
UITableView *tableView = self.tableView;
tableView.estimatedRowHeight = 80.;
tableView.rowHeight = UITableViewAutomaticDimension;
[tableView registerNibWithCellClass:[MWMSearchSuggestionCell class]];
[tableView registerNibWithCellClass:[MWMSearchCommonCell class]];
}
- (void)reloadData {
[self.tableView reloadData];
}
#pragma mark - Layout
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator
animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[self reloadData];
}
completion:nil];
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [MWMSearch resultsCount];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([MWMSearch resultsCount] == 0) {
NSAssert(false, @"Invalid reload with outdated SearchIndex");
return [tableView dequeueReusableCellWithCellClass:[MWMSearchCommonCell class] indexPath:indexPath];
}
auto const row = indexPath.row;
auto const containerIndex = [MWMSearch containerIndexWithRow:row];
auto const & result = [MWMSearch resultWithContainerIndex:containerIndex];
switch ([MWMSearch resultTypeWithRow:row])
{
case SearchItemTypeRegular:
{
auto cell = static_cast<MWMSearchCommonCell *>(
[tableView dequeueReusableCellWithCellClass:[MWMSearchCommonCell class] indexPath:indexPath]);
[cell configureWith:result isPartialMatching:YES];
return cell;
}
case SearchItemTypeSuggestion:
{
auto cell = static_cast<MWMSearchSuggestionCell *>(
[tableView dequeueReusableCellWithCellClass:[MWMSearchSuggestionCell class] indexPath:indexPath]);
[cell configureWith:result isPartialMatching:YES];
cell.isLastCell = row == [MWMSearch suggestionsCount] - 1;
return cell;
}
}
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
id<MWMSearchTableViewProtocol> delegate = self.delegate;
auto const row = indexPath.row;
auto const containerIndex = [MWMSearch containerIndexWithRow:row];
auto const & result = [MWMSearch resultWithContainerIndex:containerIndex];
switch ([MWMSearch resultTypeWithRow:row])
{
case SearchItemTypeRegular:
{
SearchTextField const * textField = delegate.searchTextField;
[MWMSearch saveQuery:textField.text forInputLocale:textField.textInputMode.primaryLanguage];
[delegate processSearchResultAtIndex:indexPath.row];
break;
}
case SearchItemTypeSuggestion:
{
[delegate searchText:result.suggestion
forInputLocale:nil
withCategory:result.isPureSuggest];
break;
}
}
}
#pragma mark - MWMSearchObserver
- (void)onSearchCompleted {
[self reloadData];
BOOL const noResults = [MWMSearch resultsCount] == 0;
self.tableView.hidden = noResults;
[(MWMSearchTableView *)self.view hideNoResultsView:!noResults];
}
@end

View file

@ -1,79 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MWMSearchTableViewController">
<connections>
<outlet property="tableView" destination="oZD-Er-6fn" id="Ayl-9n-mV7"/>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clipsSubviews="YES" contentMode="scaleToFill" id="i5M-Pr-FkT" customClass="MWMSearchTableView" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NsS-9j-1hr">
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0ed-oS-Uw6">
<rect key="frame" x="0.0" y="70" width="320" height="320"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="320" id="iu3-jC-BuA"/>
<constraint firstAttribute="width" secondItem="0ed-oS-Uw6" secondAttribute="height" multiplier="1:1" priority="999" id="rBn-RY-c74"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="0ed-oS-Uw6" firstAttribute="centerY" secondItem="NsS-9j-1hr" secondAttribute="centerY" id="3os-g7-XiN"/>
<constraint firstItem="0ed-oS-Uw6" firstAttribute="centerX" secondItem="NsS-9j-1hr" secondAttribute="centerX" id="N4N-MH-XVw"/>
<constraint firstItem="0ed-oS-Uw6" firstAttribute="height" relation="lessThanOrEqual" secondItem="NsS-9j-1hr" secondAttribute="height" id="aad-2C-r3O"/>
</constraints>
</view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="onDrag" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="oZD-Er-6fn">
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<inset key="separatorInset" minX="15" minY="0.0" maxX="0.0" maxY="0.0"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="dataSource" destination="-1" id="T9n-AG-sHf"/>
<outlet property="delegate" destination="-1" id="Syl-wX-U5b"/>
</connections>
</tableView>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="oZD-Er-6fn" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="9cU-AY-lQv"/>
<constraint firstItem="NsS-9j-1hr" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="C7g-sy-qp4"/>
<constraint firstAttribute="trailing" secondItem="oZD-Er-6fn" secondAttribute="trailing" id="DoP-hv-QXb"/>
<constraint firstAttribute="bottom" secondItem="NsS-9j-1hr" secondAttribute="bottom" priority="750" id="P9i-5E-bpw"/>
<constraint firstItem="NsS-9j-1hr" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="csW-fl-Sp0"/>
<constraint firstAttribute="trailing" secondItem="NsS-9j-1hr" secondAttribute="trailing" id="dIC-Mf-12H"/>
<constraint firstItem="oZD-Er-6fn" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="iKy-dd-6Jt"/>
<constraint firstAttribute="bottom" secondItem="oZD-Er-6fn" secondAttribute="bottom" priority="750" id="rgu-oY-7XC"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="noResultsBottomOffset" destination="P9i-5E-bpw" id="qTI-X5-KzS"/>
<outlet property="noResultsContainer" destination="NsS-9j-1hr" id="BfV-Wz-ztf"/>
<outlet property="noResultsWrapper" destination="0ed-oS-Uw6" id="RbW-yn-sl8"/>
</connections>
<point key="canvasLocation" x="139" y="154"/>
</view>
</objects>
</document>

View file

@ -1,4 +1,4 @@
protocol SearchCategoriesViewControllerDelegate: AnyObject {
protocol SearchCategoriesViewControllerDelegate: SearchOnMapScrollViewDelegate {
func categoriesViewController(_ viewController: SearchCategoriesViewController,
didSelect category: String)
}
@ -41,8 +41,19 @@ final class SearchCategoriesViewController: MWMTableViewController {
tableView.deselectRow(at: indexPath, animated: true)
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
delegate?.scrollViewDidScroll(scrollView)
}
func category(at indexPath: IndexPath) -> String {
let index = indexPath.row
return categories[index]
}
}
extension SearchCategoriesViewController: ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat) {
guard isViewLoaded else { return }
tableView.contentInset.bottom = translationY + view.safeAreaInsets.bottom
}
}

View file

@ -1,59 +1,97 @@
protocol SearchHistoryViewControllerDelegate: AnyObject {
protocol SearchHistoryViewControllerDelegate: SearchOnMapScrollViewDelegate {
func searchHistoryViewController(_ viewController: SearchHistoryViewController,
didSelect query: String)
}
final class SearchHistoryViewController: MWMViewController {
private weak var delegate: SearchHistoryViewControllerDelegate?
private var lastQueries: [String]
private var lastQueries: [String] = []
private let frameworkHelper: MWMSearchFrameworkHelper
private static let clearCellIdentifier = "SearchHistoryViewController_clearCellIdentifier"
@IBOutlet private var tableView: UITableView!
@IBOutlet private weak var noResultsViewContainer: UIView!
private let emptyHistoryView = PlaceholderView(title: L("search_history_title"),
subtitle: L("search_history_text"))
private let tableView = UITableView()
// MARK: - Init
init(frameworkHelper: MWMSearchFrameworkHelper, delegate: SearchHistoryViewControllerDelegate?) {
self.delegate = delegate
self.lastQueries = frameworkHelper.lastSearchQueries()
self.frameworkHelper = frameworkHelper
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
if frameworkHelper.isSearchHistoryEmpty() {
showNoResultsView()
} else {
tableView.register(cell: SearchHistoryCell.self)
}
setupTableView()
setupNoResultsView()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
reload()
}
// MARK: - Private methods
private func setupTableView() {
tableView.setStyle(.background)
tableView.register(cell: SearchHistoryCell.self)
tableView.keyboardDismissMode = .onDrag
tableView.delegate = self
tableView.dataSource = self
tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 1))
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
func showNoResultsView() {
guard let noResultsView = MWMSearchNoResults.view(with: nil,
title: L("search_history_title"),
text: L("search_history_text")) else {
assertionFailure()
return
private func setupNoResultsView() {
view.addSubview(emptyHistoryView)
emptyHistoryView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
emptyHistoryView.topAnchor.constraint(equalTo: view.topAnchor),
emptyHistoryView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
emptyHistoryView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
emptyHistoryView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
private func showEmptyHistoryView(_ isVisible: Bool = true, animated: Bool = true) {
UIView.transition(with: emptyHistoryView,
duration: animated ? kDefaultAnimationDuration : 0,
options: [.transitionCrossDissolve, .curveEaseInOut]) {
self.emptyHistoryView.alpha = isVisible ? 1.0 : 0.0
self.emptyHistoryView.isHidden = !isVisible
}
noResultsViewContainer.addSubview(noResultsView)
tableView.isHidden = true
}
func clearSearchHistory() {
private func clearSearchHistory() {
frameworkHelper.clearSearchHistory()
lastQueries = []
reload()
}
// MARK: - Public methods
func reload() {
guard isViewLoaded else { return }
lastQueries = frameworkHelper.lastSearchQueries()
showEmptyHistoryView(lastQueries.isEmpty ? true : false)
tableView.reloadData()
}
}
extension SearchHistoryViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return frameworkHelper.isSearchHistoryEmpty() ? 0 : lastQueries.count + 1
return lastQueries.isEmpty ? 0 : lastQueries.count + 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@ -71,16 +109,22 @@ extension SearchHistoryViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == lastQueries.count {
clearSearchHistory()
UIView.animate(withDuration: kDefaultAnimationDuration,
animations: {
tableView.alpha = 0.0
}) { _ in
self.showNoResultsView()
}
} else {
let query = lastQueries[indexPath.row]
delegate?.searchHistoryViewController(self, didSelect: query)
}
tableView.deselectRow(at: indexPath, animated: true)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
delegate?.scrollViewDidScroll(scrollView)
}
}
extension SearchHistoryViewController: ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat) {
guard isViewLoaded else { return }
tableView.contentInset.bottom = translationY
emptyHistoryView.translationYDidUpdate(translationY)
}
}

View file

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SearchHistoryViewController" customModule="Organic_Maps" customModuleProvider="target">
<connections>
<outlet property="noResultsViewContainer" destination="bcr-zs-NMw" id="zpc-sP-fbF"/>
<outlet property="tableView" destination="cDq-G7-5cR" id="Qo8-a6-Q6V"/>
<outlet property="view" destination="iN0-l3-epB" id="Ybt-gX-7O4"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="cDq-G7-5cR">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<outlet property="dataSource" destination="-1" id="XhM-2x-4kQ"/>
<outlet property="delegate" destination="-1" id="sDX-YJ-iGy"/>
</connections>
</tableView>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bcr-zs-NMw">
<rect key="frame" x="27.5" y="183.5" width="320" height="320"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="320" id="07E-Xc-KMw"/>
<constraint firstAttribute="width" constant="320" id="sCQ-Q9-Pdw"/>
</constraints>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="cDq-G7-5cR" secondAttribute="bottom" id="9QA-Kd-FQb"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="cDq-G7-5cR" secondAttribute="trailing" id="T3b-23-qG6"/>
<constraint firstItem="bcr-zs-NMw" firstAttribute="centerY" secondItem="vUN-kp-3ea" secondAttribute="centerY" id="Y7z-3L-jl5"/>
<constraint firstItem="cDq-G7-5cR" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="ezl-g4-mlD"/>
<constraint firstItem="cDq-G7-5cR" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="ot8-FU-S6n"/>
<constraint firstItem="bcr-zs-NMw" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="uMb-A3-NrS"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<point key="canvasLocation" x="53.600000000000001" y="48.125937031484263"/>
</view>
</objects>
</document>

View file

@ -1,12 +0,0 @@
#import "MWMSearchManagerState.h"
@protocol MWMSearchTabbedViewProtocol <NSObject>
@required
@property(nonatomic) MWMSearchManagerState state;
- (void)searchText:(NSString *)text forInputLocale:(NSString *)locale withCategory:(BOOL)isCategory;
- (void)dismissKeyboard;
@end

View file

@ -1,5 +1,5 @@
@objc(MWMSearchTabViewControllerDelegate)
protocol SearchTabViewControllerDelegate: AnyObject {
protocol SearchTabViewControllerDelegate: SearchOnMapScrollViewDelegate {
func searchTabController(_ viewController: SearchTabViewController, didSearch: String, withCategory: Bool)
}
@ -47,6 +47,22 @@ final class SearchTabViewController: TabViewController {
super.viewDidDisappear(animated)
activeTab = SearchActiveTab.init(rawValue: tabView.selectedIndex ?? 0) ?? .categories
}
func reloadSearchHistory() {
(viewControllers[SearchActiveTab.history.rawValue] as? SearchHistoryViewController)?.reload()
}
}
extension SearchTabViewController: ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat) {
viewControllers.forEach { ($0 as? ModallyPresentedViewController)?.translationYDidUpdate(translationY) }
}
}
extension SearchTabViewController: SearchOnMapScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
delegate?.scrollViewDidScroll(scrollView)
}
}
extension SearchTabViewController: SearchCategoriesViewControllerDelegate {

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Wns-nH-AQU">
<device id="retina6_72" orientation="landscape" appearance="light"/>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Wns-nH-AQU">
<device id="iPad13_0rounded" orientation="portrait" layout="fullscreen" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -14,26 +14,26 @@
<objects>
<viewController id="xTf-lf-yxN" customClass="MapViewController" sceneMemberID="viewController">
<view key="view" clearsContextBeforeDrawing="NO" multipleTouchEnabled="YES" contentMode="scaleToFill" id="USG-6L-Uhw">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ixC-IZ-Pvs" userLabel="CarPlayPlaceholderView" customClass="CarplayPlaceholderView" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aPn-pa-nCx" customClass="EAGLView">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<color key="backgroundColor" red="0.8666666666666667" green="0.8666666666666667" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rL1-9E-4b7">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<subviews>
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="65S-M4-TnM" customClass="NavigationInfoArea" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="59" y="0.0" width="814" height="409"/>
<rect key="frame" x="0.0" y="24" width="1032" height="1332"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="1" alpha="0.20000000000000001" colorSpace="calibratedRGB"/>
</view>
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TdT-ia-GP9">
<rect key="frame" x="59" y="0.0" width="350" height="409"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1356"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="1" alpha="0.20000000000000001" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="width" constant="350" id="XLL-zv-Bym"/>
@ -50,7 +50,7 @@
</variation>
</view>
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aVk-ab-LFJ" customClass="TabBarArea" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="59" y="361" width="350" height="69"/>
<rect key="frame" x="0.0" y="1308" width="1032" height="68"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="1" alpha="0.20000000000000001" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="width" priority="100" constant="350" id="aj4-lb-h52"/>
@ -67,26 +67,26 @@
</variation>
</view>
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="awj-9E-eBS" customClass="PlacePageArea" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="59" y="0.0" width="814" height="409"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1356"/>
<color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="0.20000000000000001" colorSpace="calibratedRGB"/>
</view>
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="FFY-Dy-Wou" customClass="VisibleArea" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="59" y="0.0" width="814" height="409"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1356"/>
<color key="backgroundColor" red="0.0" green="1" blue="0.0" alpha="0.20000000000000001" colorSpace="calibratedRGB"/>
</view>
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NI8-tV-i2B" customClass="WidgetsArea" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="59" y="0.0" width="814" height="409"/>
<rect key="frame" x="0.0" y="24" width="1032" height="1332"/>
<color key="backgroundColor" red="0.0" green="1" blue="0.0" alpha="0.20000000000000001" colorSpace="calibratedRGB"/>
</view>
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xJx-UU-IdV" customClass="SideButtonsArea" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="59" y="0.0" width="814" height="409"/>
<rect key="frame" x="0.0" y="24" width="1032" height="1332"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="1" alpha="0.20000000000000001" colorSpace="calibratedRGB"/>
</view>
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QKu-4A-UgP" customClass="TrafficButtonArea" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="59" y="0.0" width="814" height="409"/>
<rect key="frame" x="0.0" y="24" width="1032" height="1332"/>
</view>
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yto-Gu-Ugz" userLabel="Track Recording Button Area" customClass="TrackRecordingButtonArea" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="0.0" y="59" width="430" height="839"/>
<rect key="frame" x="0.0" y="24" width="1032" height="1332"/>
<color key="backgroundColor" red="0.0" green="1" blue="0.0" alpha="0.20000000000000001" colorSpace="calibratedRGB"/>
</view>
</subviews>
@ -98,64 +98,8 @@
<constraint firstItem="FFY-Dy-Wou" firstAttribute="top" secondItem="rL1-9E-4b7" secondAttribute="top" priority="100" id="svT-Vi-vvC"/>
</constraints>
</view>
<view contentMode="scaleToFill" placeholderIntrinsicWidth="infinite" placeholderIntrinsicHeight="500" translatesAutoresizingMaskIntoConstraints="NO" id="jio-3T-E6G" customClass="TouchTransparentView" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="59" y="0.0" width="350" height="430"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="350" id="Dd0-x6-7gc">
<variation key="heightClass=regular-widthClass=regular" constant="320"/>
</constraint>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="Dd0-x6-7gc"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=compact">
<mask key="constraints">
<include reference="Dd0-x6-7gc"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=regular">
<mask key="constraints">
<include reference="Dd0-x6-7gc"/>
</mask>
</variation>
<variation key="heightClass=regular-widthClass=regular">
<mask key="constraints">
<include reference="Dd0-x6-7gc"/>
</mask>
</variation>
</view>
<view hidden="YES" contentMode="scaleToFill" placeholderIntrinsicWidth="infinite" placeholderIntrinsicHeight="500" translatesAutoresizingMaskIntoConstraints="NO" id="rbx-Oj-jeo" customClass="TouchTransparentView" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="59" y="0.0" width="350" height="430"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="350" id="6h8-a6-LWn"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="6h8-a6-LWn"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=compact">
<mask key="constraints">
<include reference="6h8-a6-LWn"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=regular">
<mask key="constraints">
<include reference="6h8-a6-LWn"/>
</mask>
</variation>
<variation key="heightClass=regular-widthClass=regular">
<mask key="constraints">
<include reference="6h8-a6-LWn"/>
</mask>
</variation>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="at1-V1-pzl" customClass="TouchTransparentView" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="59" y="209" width="814" height="152"/>
<rect key="frame" x="0.0" y="722" width="440" height="152"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<gestureRecognizers/>
<constraints>
@ -171,14 +115,11 @@
<constraints>
<constraint firstItem="utd-Jy-pE5" firstAttribute="bottom" secondItem="65S-M4-TnM" secondAttribute="bottom" priority="100" id="2E3-5i-6An"/>
<constraint firstAttribute="bottom" secondItem="rL1-9E-4b7" secondAttribute="bottom" id="4fw-Xu-gAG"/>
<constraint firstItem="rbx-Oj-jeo" firstAttribute="leading" secondItem="utd-Jy-pE5" secondAttribute="leading" id="5Sh-l6-Icd"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="bottom" secondItem="QKu-4A-UgP" secondAttribute="bottom" priority="100" id="6ko-rI-S5u"/>
<constraint firstAttribute="bottom" secondItem="ixC-IZ-Pvs" secondAttribute="bottom" id="7eh-og-Mpe"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="trailing" secondItem="aVk-ab-LFJ" secondAttribute="trailing" id="85b-Do-jO7"/>
<constraint firstItem="at1-V1-pzl" firstAttribute="top" secondItem="utd-Jy-pE5" secondAttribute="bottom" priority="250" id="8JV-dP-iYZ"/>
<constraint firstItem="TdT-ia-GP9" firstAttribute="leading" secondItem="utd-Jy-pE5" secondAttribute="leading" id="8YH-dJ-lHZ"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="trailing" secondItem="rbx-Oj-jeo" secondAttribute="trailing" id="9M9-8P-Hzb"/>
<constraint firstAttribute="bottom" secondItem="rbx-Oj-jeo" secondAttribute="bottom" id="9rR-QQ-c1P"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="top" secondItem="xJx-UU-IdV" secondAttribute="top" priority="100" id="BMq-jc-qfO"/>
<constraint firstItem="ixC-IZ-Pvs" firstAttribute="top" secondItem="USG-6L-Uhw" secondAttribute="top" id="DSJ-0D-hwO"/>
<constraint firstItem="rL1-9E-4b7" firstAttribute="top" secondItem="USG-6L-Uhw" secondAttribute="top" id="E89-WV-ZTh"/>
@ -187,8 +128,6 @@
<constraint firstItem="utd-Jy-pE5" firstAttribute="trailing" secondItem="at1-V1-pzl" secondAttribute="trailing" id="GKG-4Y-2Pq"/>
<constraint firstAttribute="trailing" secondItem="aPn-pa-nCx" secondAttribute="trailing" id="GKJ-zm-8xb"/>
<constraint firstItem="awj-9E-eBS" firstAttribute="leading" secondItem="utd-Jy-pE5" secondAttribute="leading" id="Hfm-gb-37H"/>
<constraint firstItem="rbx-Oj-jeo" firstAttribute="top" secondItem="utd-Jy-pE5" secondAttribute="top" id="J0B-xe-sNj"/>
<constraint firstItem="jio-3T-E6G" firstAttribute="leading" secondItem="utd-Jy-pE5" secondAttribute="leading" id="K9r-P1-yFI"/>
<constraint firstAttribute="trailing" secondItem="ixC-IZ-Pvs" secondAttribute="trailing" id="O7f-qU-UN2"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="bottom" secondItem="at1-V1-pzl" secondAttribute="bottom" priority="750" constant="48" id="O8L-nd-nOa"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="bottom" secondItem="FFY-Dy-Wou" secondAttribute="bottom" priority="100" id="OE7-Qb-J0v"/>
@ -196,13 +135,11 @@
<constraint firstItem="utd-Jy-pE5" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="awj-9E-eBS" secondAttribute="bottom" id="PFs-sL-oVA"/>
<constraint firstAttribute="trailing" secondItem="rL1-9E-4b7" secondAttribute="trailing" id="QdS-Yx-ADV"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="trailing" secondItem="TdT-ia-GP9" secondAttribute="trailing" id="Rsb-fB-8bn"/>
<constraint firstItem="jio-3T-E6G" firstAttribute="leading" secondItem="utd-Jy-pE5" secondAttribute="leading" constant="-320" id="SAj-bF-qrr"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="xJx-UU-IdV" secondAttribute="bottom" id="SDX-4J-Jz5"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="bottom" secondItem="FFY-Dy-Wou" secondAttribute="bottom" id="TZk-MH-pMV"/>
<constraint firstItem="yto-Gu-Ugz" firstAttribute="leading" secondItem="utd-Jy-pE5" secondAttribute="leading" id="TgS-dg-g9B"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="trailing" secondItem="yto-Gu-Ugz" secondAttribute="trailing" id="URB-3l-JiC"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="bottom" secondItem="xJx-UU-IdV" secondAttribute="bottom" priority="100" id="VfU-Zk-8IU"/>
<constraint firstItem="rbx-Oj-jeo" firstAttribute="leading" secondItem="jio-3T-E6G" secondAttribute="trailing" constant="20" id="W0l-NG-7lt"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="top" secondItem="QKu-4A-UgP" secondAttribute="top" priority="100" id="X96-y7-5oI"/>
<constraint firstItem="aPn-pa-nCx" firstAttribute="leading" secondItem="USG-6L-Uhw" secondAttribute="leading" id="YFX-ma-vAf"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="FFY-Dy-Wou" secondAttribute="bottom" id="YUs-MJ-9w8"/>
@ -217,18 +154,14 @@
<constraint firstItem="xJx-UU-IdV" firstAttribute="leading" secondItem="utd-Jy-pE5" secondAttribute="leading" id="gn8-NV-oAa"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="bottom" secondItem="aVk-ab-LFJ" secondAttribute="top" priority="100" constant="48" id="jQI-62-O5O"/>
<constraint firstItem="65S-M4-TnM" firstAttribute="leading" secondItem="utd-Jy-pE5" secondAttribute="leading" id="kan-ei-cIg"/>
<constraint firstAttribute="bottom" secondItem="jio-3T-E6G" secondAttribute="bottom" id="l6r-eW-Dbi"/>
<constraint firstItem="NI8-tV-i2B" firstAttribute="leading" secondItem="utd-Jy-pE5" secondAttribute="leading" id="lwX-Xm-J8A"/>
<constraint firstItem="ixC-IZ-Pvs" firstAttribute="leading" secondItem="USG-6L-Uhw" secondAttribute="leading" id="m7B-SH-DDh"/>
<constraint firstItem="FFY-Dy-Wou" firstAttribute="leading" secondItem="utd-Jy-pE5" secondAttribute="leading" id="pc3-CW-vyV"/>
<constraint firstItem="aPn-pa-nCx" firstAttribute="top" secondItem="USG-6L-Uhw" secondAttribute="top" id="pwE-hX-IML"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="bottom" secondItem="TdT-ia-GP9" secondAttribute="bottom" priority="100" id="pwZ-Fm-mHR"/>
<constraint firstItem="aVk-ab-LFJ" firstAttribute="leading" secondItem="utd-Jy-pE5" secondAttribute="leading" id="qtc-CV-Hae"/>
<constraint firstAttribute="top" secondItem="jio-3T-E6G" secondAttribute="top" id="rYG-px-wH5"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="top" secondItem="NI8-tV-i2B" secondAttribute="top" priority="100" id="sSU-QE-9zI"/>
<constraint firstAttribute="trailing" secondItem="jio-3T-E6G" secondAttribute="trailing" id="t9l-Ud-h6j"/>
<constraint firstAttribute="bottom" secondItem="aPn-pa-nCx" secondAttribute="bottom" id="tB3-eX-gUV"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="rbx-Oj-jeo" secondAttribute="bottom" constant="70" id="u9s-KY-yCt"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="bottom" secondItem="xJx-UU-IdV" secondAttribute="bottom" id="veF-Rn-BEm"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="top" secondItem="65S-M4-TnM" secondAttribute="top" priority="100" id="vfQ-ZT-Dlc"/>
<constraint firstItem="utd-Jy-pE5" firstAttribute="trailing" secondItem="awj-9E-eBS" secondAttribute="trailing" id="wGo-EW-X9f"/>
@ -240,10 +173,6 @@
<exclude reference="SDX-4J-Jz5"/>
<exclude reference="TZk-MH-pMV"/>
<exclude reference="veF-Rn-BEm"/>
<exclude reference="u9s-KY-yCt"/>
<exclude reference="K9r-P1-yFI"/>
<exclude reference="SAj-bF-qrr"/>
<exclude reference="W0l-NG-7lt"/>
</mask>
</variation>
<variation key="heightClass=compact">
@ -252,7 +181,6 @@
<exclude reference="Rsb-fB-8bn"/>
<include reference="TZk-MH-pMV"/>
<include reference="veF-Rn-BEm"/>
<include reference="K9r-P1-yFI"/>
</mask>
</variation>
<variation key="heightClass=regular">
@ -260,34 +188,6 @@
<include reference="SDX-4J-Jz5"/>
</mask>
</variation>
<variation key="widthClass=compact">
<mask key="constraints">
<include reference="K9r-P1-yFI"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=compact">
<mask key="constraints">
<exclude reference="9M9-8P-Hzb"/>
<exclude reference="t9l-Ud-h6j"/>
</mask>
</variation>
<variation key="heightClass=compact-widthClass=regular">
<mask key="constraints">
<exclude reference="9M9-8P-Hzb"/>
<exclude reference="t9l-Ud-h6j"/>
</mask>
</variation>
<variation key="heightClass=regular-widthClass=regular">
<mask key="constraints">
<exclude reference="9M9-8P-Hzb"/>
<include reference="u9s-KY-yCt"/>
<exclude reference="t9l-Ud-h6j"/>
<include reference="SAj-bF-qrr"/>
<exclude reference="9rR-QQ-c1P"/>
<include reference="W0l-NG-7lt"/>
<exclude reference="5Sh-l6-Icd"/>
</mask>
</variation>
</view>
<navigationItem key="navigationItem" id="8E8-0f-UV9"/>
<connections>
@ -295,9 +195,6 @@
<outlet property="controlsView" destination="rL1-9E-4b7" id="sfV-7X-WlR"/>
<outlet property="mapView" destination="aPn-pa-nCx" id="tCi-LW-1ll"/>
<outlet property="placePageAreaKeyboard" destination="PFs-sL-oVA" id="O3P-ia-ZlX"/>
<outlet property="placePageContainer" destination="rbx-Oj-jeo" id="aFM-qm-QHB"/>
<outlet property="searchViewContainer" destination="jio-3T-E6G" id="Rjn-UE-zFx"/>
<outlet property="searchViewContainerLeadingConstraint" destination="SAj-bF-qrr" id="LJL-dW-eMM"/>
<outlet property="sideButtonsAreaBottom" destination="VfU-Zk-8IU" id="MvP-Ki-4wP"/>
<outlet property="sideButtonsAreaKeyboard" destination="SDX-4J-Jz5" id="kv9-zX-hbD"/>
<outlet property="visibleAreaBottom" destination="OE7-Qb-J0v" id="isp-aT-LtA"/>
@ -342,7 +239,7 @@
<objects>
<tableViewController storyboardIdentifier="MWMEditBookmarkController" id="lFr-lA-JTW" customClass="MWMEditBookmarkController" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="Rb3-ea-7LJ">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
@ -362,7 +259,7 @@
<navigationController id="Psz-BY-Fy4" customClass="MWMNavigationController" sceneMemberID="viewController">
<value key="contentSizeForViewInPopover" type="size" width="600" height="600"/>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="SUN-3A-xgM">
<rect key="frame" x="0.0" y="0.0" width="932" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="56"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
@ -378,7 +275,7 @@
<objects>
<tableViewController id="Lfa-Zp-orR" customClass="MWMEditorViewController" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="44" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="HU6-ak-Eu1">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
@ -404,11 +301,11 @@
<objects>
<viewController id="Ld6-gM-2hk" customClass="MWMOpeningHoursEditorViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="U1f-hD-9rl">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" dataMode="prototypes" style="grouped" separatorStyle="none" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="X1H-IB-Nv1">
<rect key="frame" x="0.0" y="0.0" width="932" height="365"/>
<rect key="frame" x="0.0" y="24" width="1032" height="1288"/>
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="PressBackground"/>
@ -419,10 +316,10 @@
</connections>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aQv-7U-zAP">
<rect key="frame" x="59" y="0.0" width="814" height="365"/>
<rect key="frame" x="0.0" y="24" width="1032" height="1288"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="PrH-u2-IEv" userLabel="Editor View" customClass="MWMTextView">
<rect key="frame" x="0.0" y="36" width="814" height="88"/>
<rect key="frame" x="0.0" y="36" width="1032" height="88"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="88" id="oAE-yX-hVe"/>
@ -439,7 +336,7 @@
</connections>
</textView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="separator_image" translatesAutoresizingMaskIntoConstraints="NO" id="z2Z-G2-Np7">
<rect key="frame" x="0.0" y="35" width="814" height="1"/>
<rect key="frame" x="0.0" y="35" width="1032" height="1"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="xUX-ck-MQb"/>
</constraints>
@ -448,7 +345,7 @@
</userDefinedRuntimeAttributes>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="separator_image" translatesAutoresizingMaskIntoConstraints="NO" id="ebA-fW-ddJ">
<rect key="frame" x="0.0" y="123" width="814" height="1"/>
<rect key="frame" x="0.0" y="123" width="1032" height="1"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="HOj-tZ-b3F"/>
</constraints>
@ -457,7 +354,7 @@
</userDefinedRuntimeAttributes>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="separator_image" translatesAutoresizingMaskIntoConstraints="NO" id="5T5-Pp-hb5">
<rect key="frame" x="0.0" y="320" width="814" height="1"/>
<rect key="frame" x="0.0" y="1243" width="1032" height="1"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="ZXK-zv-uSz"/>
</constraints>
@ -466,7 +363,7 @@
</userDefinedRuntimeAttributes>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="separator_image" translatesAutoresizingMaskIntoConstraints="NO" id="IX2-yp-0oa">
<rect key="frame" x="0.0" y="164" width="814" height="1"/>
<rect key="frame" x="0.0" y="164" width="1032" height="1"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="rD4-fE-ez2"/>
</constraints>
@ -475,13 +372,13 @@
</userDefinedRuntimeAttributes>
</imageView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="85Z-MR-kUV" userLabel="Help View">
<rect key="frame" x="0.0" y="164" width="814" height="328"/>
<rect key="frame" x="0.0" y="164" width="1032" height="324"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="n79-h1-Nk3" userLabel="Button">
<rect key="frame" x="0.0" y="0.0" width="814" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="44"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Example values" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="120" translatesAutoresizingMaskIntoConstraints="NO" id="dAM-iT-fzu">
<rect key="frame" x="16.000000000000007" y="12" width="117.66666666666669" height="20"/>
<rect key="frame" x="16" y="12" width="117.5" height="20"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@ -491,7 +388,7 @@
</userDefinedRuntimeAttributes>
</label>
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_arrow_gray_down" translatesAutoresizingMaskIntoConstraints="NO" id="m7d-sG-5LN">
<rect key="frame" x="782" y="10" width="24" height="24"/>
<rect key="frame" x="1000" y="10" width="24" height="24"/>
<constraints>
<constraint firstAttribute="height" constant="24" id="2aF-WV-ER2"/>
<constraint firstAttribute="width" constant="24" id="eak-KY-Xaa"/>
@ -501,7 +398,7 @@
</userDefinedRuntimeAttributes>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="separator_image" translatesAutoresizingMaskIntoConstraints="NO" id="Suj-t5-ZWs">
<rect key="frame" x="16" y="44" width="798" height="1"/>
<rect key="frame" x="16" y="44" width="1016" height="1"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="cv8-Tg-Oin"/>
</constraints>
@ -510,7 +407,7 @@
</userDefinedRuntimeAttributes>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MiP-Du-s3i">
<rect key="frame" x="0.0" y="0.0" width="814" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="44"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<action selector="toggleExample" destination="Ld6-gM-2hk" eventType="touchUpInside" id="BGK-Ap-YBq"/>
@ -537,7 +434,7 @@
</userDefinedRuntimeAttributes>
</view>
<wkWebView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Gff-2B-vhp">
<rect key="frame" x="10" y="54" width="373" height="260"/>
<rect key="frame" x="10" y="54" width="1012" height="260"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" priority="750" constant="260" id="NdE-kI-erk"/>
@ -587,10 +484,10 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SZQ-ra-FC3" userLabel="Mode Switch">
<rect key="frame" x="0.0" y="365" width="932" height="44"/>
<rect key="frame" x="0.0" y="1312" width="1032" height="44"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fB1-w2-lJI">
<rect key="frame" x="0.0" y="0.0" width="932" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="44"/>
<inset key="contentEdgeInsets" minX="16" minY="0.0" maxX="0.0" maxY="0.0"/>
<state key="normal" title="Simple Mode"/>
<userDefinedRuntimeAttributes>
@ -601,7 +498,7 @@
</connections>
</button>
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_arrow_gray_right" translatesAutoresizingMaskIntoConstraints="NO" id="z8F-55-5rJ">
<rect key="frame" x="900" y="10" width="24" height="24"/>
<rect key="frame" x="1000" y="10" width="24" height="24"/>
<constraints>
<constraint firstAttribute="width" constant="24" id="ypP-17-bfX"/>
<constraint firstAttribute="height" constant="24" id="zE5-1N-qvh"/>
@ -611,7 +508,7 @@
</userDefinedRuntimeAttributes>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="separator_image" translatesAutoresizingMaskIntoConstraints="NO" id="onT-vv-01i">
<rect key="frame" x="0.0" y="-1" width="932" height="1"/>
<rect key="frame" x="0.0" y="-1" width="1032" height="1"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="UUW-c4-eNA"/>
</constraints>
@ -679,26 +576,26 @@
<objects>
<tableViewController id="ocL-kj-jxR" customClass="MWMEditorAdditionalNamesTableViewController" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="tQ2-XI-QWd">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="ListCellIdentifier" textLabel="JcK-nR-UGw" detailTextLabel="Cmi-x5-6Vt" style="IBUITableViewCellStyleSubtitle" id="RXe-xp-xlR" customClass="MWMTableViewCell">
<rect key="frame" x="0.0" y="50" width="932" height="44"/>
<rect key="frame" x="0.0" y="50" width="1032" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="RXe-xp-xlR" id="g0x-Vt-1FI">
<rect key="frame" x="59" y="0.0" width="814" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="JcK-nR-UGw">
<rect key="frame" x="20" y="6" width="31.666666666666668" height="19.333333333333332"/>
<rect key="frame" x="20" y="6" width="31.5" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Detail" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Cmi-x5-6Vt">
<rect key="frame" x="20" y="25.333333333333332" width="30.333333333333332" height="13.333333333333334"/>
<rect key="frame" x="20" y="25.5" width="30.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -723,7 +620,7 @@
<objects>
<tableViewController id="Heu-QR-M0N" customClass="MWMStreetEditorViewController" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" id="rJJ-UB-6u2">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<inset key="scrollIndicatorInsets" minX="0.0" minY="44" maxX="0.0" maxY="0.0"/>
@ -742,11 +639,11 @@
<objects>
<viewController id="QlF-CJ-cEG" customClass="MWMObjectsCategorySelectorController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="MIY-NW-Joh">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="JbV-y9-HBo">
<rect key="frame" x="0.0" y="56" width="932" height="353"/>
<rect key="frame" x="0.0" y="80" width="1032" height="1276"/>
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="TableView:PressBackground"/>
@ -757,7 +654,7 @@
</connections>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rI9-RR-sKP" userLabel="Status Bar Background">
<rect key="frame" x="0.0" y="-52" width="932" height="108"/>
<rect key="frame" x="0.0" y="-28" width="1032" height="108"/>
<color key="backgroundColor" red="0.1215686275" green="0.59999999999999998" blue="0.32156862749999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="tintColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
@ -768,7 +665,7 @@
</userDefinedRuntimeAttributes>
</view>
<searchBar contentMode="redraw" translatesAutoresizingMaskIntoConstraints="NO" id="gzF-B7-8pj">
<rect key="frame" x="59" y="0.0" width="873" height="56"/>
<rect key="frame" x="0.0" y="24" width="1032" height="56"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="44" id="2uI-k6-ahr"/>
@ -813,11 +710,11 @@
<objects>
<viewController id="da4-KT-kzF" customClass="MWMCuisineEditorViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="iTG-qE-svw">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="ina-WD-kps">
<rect key="frame" x="0.0" y="56" width="932" height="353"/>
<rect key="frame" x="0.0" y="80" width="1032" height="1276"/>
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="TableView:PressBackground"/>
@ -828,7 +725,7 @@
</connections>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HEU-Bu-3wh" userLabel="Status Bar Background">
<rect key="frame" x="0.0" y="-52" width="932" height="108"/>
<rect key="frame" x="0.0" y="-28" width="1032" height="108"/>
<color key="backgroundColor" red="0.1215686275" green="0.59999999999999998" blue="0.32156862749999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="tintColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
@ -839,7 +736,7 @@
</userDefinedRuntimeAttributes>
</view>
<searchBar contentMode="redraw" translatesAutoresizingMaskIntoConstraints="NO" id="z6s-26-dP6">
<rect key="frame" x="59" y="0.0" width="814" height="56"/>
<rect key="frame" x="0.0" y="24" width="1032" height="56"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="44" id="UAk-z1-2EY"/>
@ -883,11 +780,11 @@
<objects>
<viewController storyboardIdentifier="DownloadMapsViewController" id="h4a-ne-bSJ" customClass="MWMDownloadMapsViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="XQZ-0V-SyR">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="-1" estimatedSectionHeaderHeight="-1" sectionFooterHeight="1" translatesAutoresizingMaskIntoConstraints="NO" id="CwW-x8-G3j">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<gestureRecognizers/>
<userDefinedRuntimeAttributes>
@ -899,13 +796,13 @@
</connections>
</tableView>
<containerView hidden="YES" opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kXO-Oh-2vO" userLabel="No Maps Container View">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<connections>
<segue destination="b8o-rZ-x0k" kind="embed" identifier="MapDownloaderNoResultsEmbedViewControllerSegue" id="ish-dC-mkH"/>
</connections>
</containerView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="CDj-ol-HRP">
<rect key="frame" x="0.0" y="345" width="932" height="85"/>
<rect key="frame" x="0.0" y="1292" width="1032" height="84"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="PressBackground"/>
@ -948,20 +845,20 @@
<objects>
<viewController storyboardIdentifier="MWMNoMapsViewController" id="3el-Zi-2E4" customClass="MWMNoMapsViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="4WP-vj-Alg">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Gmw-e3-n53" userLabel="Container" customClass="MWMNoMapsView">
<rect key="frame" x="258.33333333333326" y="0.0" width="415.66666666666674" height="409"/>
<rect key="frame" x="13" y="24" width="367" height="804"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dCZ-PN-2Ob" userLabel="BoundsView">
<rect key="frame" x="16" y="0.0" width="383.66666666666669" height="409"/>
<rect key="frame" x="16" y="0.0" width="335" height="804"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="87G-jh-N8H" userLabel="CenteredView">
<rect key="frame" x="0.0" y="149.66666666666666" width="383.66666666666669" height="109.66666666666666"/>
<rect key="frame" x="0.0" y="347.5" width="335" height="109.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="У вас нет загруженных карт" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="abh-G0-Alr" userLabel="Title">
<rect key="frame" x="0.0" y="40" width="383.66666666666669" height="24"/>
<rect key="frame" x="0.0" y="40" width="335" height="24"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="20"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@ -971,7 +868,7 @@
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Загрузите необходимые карты, чтобы находить места и пользоваться навигацией без интернета." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="LaW-Ad-mYI" userLabel="Text">
<rect key="frame" x="0.0" y="76" width="383.66666666666669" height="33.666666666666657"/>
<rect key="frame" x="0.0" y="76" width="335" height="33.5"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@ -1029,7 +926,7 @@
</connections>
</view>
<button opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Moj-UK-oyl" userLabel="DownloadMaps" customClass="MWMButton">
<rect key="frame" x="346" y="325" width="240" height="44"/>
<rect key="frame" x="76.5" y="748" width="240" height="44"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="49x-bx-JJj"/>
@ -1074,11 +971,11 @@
<objects>
<viewController storyboardIdentifier="SearchNoResultsViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="0VQ-EO-9Sv" customClass="SearchNoResultsViewController" customModule="Organic_Maps" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="9jm-RW-DZK">
<rect key="frame" x="0.0" y="0.0" width="932" height="430"/>
<rect key="frame" x="0.0" y="0.0" width="1032" height="1376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="EbW-Mp-c6s">
<rect key="frame" x="0.0" y="0.0" width="932" height="409"/>
<rect key="frame" x="0.0" y="172" width="1032" height="1032"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" secondItem="EbW-Mp-c6s" secondAttribute="height" multiplier="1:1" id="tBC-sb-Q3g"/>