Compare commits

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

3 commits

Author SHA1 Message Date
200b433f71 [ios] fix layout crash on the ios 12 on ipad
The exception was rised by the NSLayoutConstrait when the priority is changed (on ipad) after constraint activation. The all of the constraints set up shood be done before activation.

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-12 17:15:40 +04:00
a2b642d601 [ios] fix pasting coords to the search
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-12 17:15:40 +04:00
f53ed1a9ef [ios] replace the modally presented search vc presentation with child
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-12 17:15:40 +04:00
17 changed files with 214 additions and 303 deletions

View file

@ -52,5 +52,6 @@
@property(nonatomic) MWMMyPositionMode currentPositionMode;
@property(strong, nonatomic) IBOutlet EAGLView * _Nonnull mapView;
@property(strong, nonatomic) IBOutlet UIView * _Nonnull controlsView;
@property(nonatomic) UIView * _Nonnull searchContainer;
@end

View file

@ -148,34 +148,45 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (void)setupPlacePageContainer {
self.placePageContainer = [[TouchTransparentView alloc] initWithFrame:self.view.bounds];
self.placePageContainer.translatesAutoresizingMaskIntoConstraints = NO;
[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;
if (IPAD)
self.placePageLeadingConstraint.priority = UILayoutPriorityDefaultLow;
self.placePageWidthConstraint = [self.placePageContainer.widthAnchor constraintEqualToConstant:0];
self.placePageWidthConstraint = [self.placePageContainer.widthAnchor constraintEqualToConstant:kPlacePageCompactWidth];
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;
}
NSLayoutConstraint * topConstraint = [self.placePageContainer.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor];
NSLayoutConstraint * bottomConstraint;
if (IPAD)
bottomConstraint = [self.placePageContainer.bottomAnchor constraintLessThanOrEqualToAnchor:self.view.bottomAnchor];
else
bottomConstraint = [self.placePageContainer.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor];
[NSLayoutConstraint activateConstraints:@[
self.placePageLeadingConstraint,
topConstraint,
bottomConstraint,
]];
[self updatePlacePageContainerConstraints];
}
- (void)setupSearchContainer {
self.searchContainer = [[TouchTransparentView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.searchContainer];
[self.view bringSubviewToFront:self.searchContainer];
}
- (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];
if (IPAD && self.searchView != nil) {
NSLayoutConstraint * leadingToSearchConstraint = [self.placePageContainer.leadingAnchor constraintEqualToAnchor:self.searchView.trailingAnchor constant:kPlacePageLeadingOffset];
leadingToSearchConstraint.priority = UILayoutPriorityDefaultHigh;
leadingToSearchConstraint.active = isLimitedWidth;
}
@ -259,9 +270,6 @@ NSString *const kSettingsSegue = @"Map2Settings";
return;
}
if (self.searchManager.isSearching && type == df::TouchEvent::TOUCH_MOVE)
[self.searchManager setMapIsDragging];
NSArray *allTouches = [[event allTouches] allObjects];
if ([allTouches count] < 1)
return;
@ -273,6 +281,10 @@ NSString *const kSettingsSegue = @"Map2Settings";
UITouch *touch = [allTouches objectAtIndex:0];
CGPoint const pt = [touch locationInView:v];
// **Check if the tap is inside searchView**
if (self.searchManager.isSearching && type == df::TouchEvent::TOUCH_MOVE && !CGRectContainsPoint(self.searchView.frame, pt))
[self.searchManager setMapIsDragging];
e.SetTouchType(type);
df::Touch t0;
@ -372,6 +384,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (void)viewDidLoad {
[super viewDidLoad];
[self setupPlacePageContainer];
[self setupSearchContainer];
if (@available(iOS 14.0, *))
[self setupTrackPadGestureRecognizers];
@ -726,11 +739,11 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (SearchOnMapManager *)searchManager {
if (!_searchManager)
_searchManager = [[SearchOnMapManager alloc] initWithNavigationController:self.navigationController];
_searchManager = [[SearchOnMapManager alloc] init];
return _searchManager;
}
- (UIView * _Nullable)searchViewContainer {
- (UIView * _Nullable)searchView {
return self.searchManager.viewController.view;
}

View file

@ -61,7 +61,7 @@ using Observers = NSHashTable<Observer>;
- (void)searchEverywhere {
self.lastSearchTimestamp += 1;
NSUInteger const timestamp = self.lastSearchTimestamp;
search::EverywhereSearchParams params{
m_query, m_locale, {} /* default timeout */, m_isCategory,
// m_onResults
@ -156,6 +156,7 @@ using Observers = NSHashTable<Observer>;
+ (void)showResultAtIndex:(NSUInteger)index {
auto const & result = [MWMSearch manager]->m_everywhereResults[index];
GetFramework().StopLocationFollow();
GetFramework().SelectSearchResult(result, true);
}
@ -168,8 +169,13 @@ using Observers = NSHashTable<Observer>;
+ (void)showEverywhereSearchResultsOnMap {
MWMSearch * manager = [MWMSearch manager];
if (![MWMRouter isRoutingActive])
GetFramework().ShowSearchResults(manager->m_everywhereResults);
if (![MWMRouter isRoutingActive]) {
auto const & results = manager->m_everywhereResults;
if (results.GetCount() == 1)
[self showResultAtIndex:0];
else
GetFramework().ShowSearchResults(manager->m_everywhereResults);
}
}
+ (void)showViewportSearchResultsOnMap {

View file

@ -1,5 +1,6 @@
enum SearchStyleSheet: String, CaseIterable {
case searchHeader
case searchCancelButton
case searchInstallButton = "SearchInstallButton"
case searchBanner = "SearchBanner"
case searchClosedBackground = "SearchClosedBackground"
@ -103,6 +104,13 @@ extension SearchStyleSheet: IStyleSheet {
return .addFrom(GlobalStyleSheet.tableCell) { s in
s.backgroundColor = colors.transparentGreen
}
case .searchCancelButton:
return .add { s in
s.fontColor = colors.whitePrimaryText
s.fontColorHighlighted = colors.whitePrimaryTextHighlighted
s.font = fonts.regular17
s.backgroundColor = .clear
}
}
}
}

View file

@ -495,16 +495,12 @@
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 */; };
ED70D5902D539A2500738C1E /* SearchOnMapPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57D2D539A2500738C1E /* SearchOnMapPresentationController.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 */; };
@ -1390,6 +1386,10 @@
A630D205207CAA3A00976DEA /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
A630D206207CAA5800976DEA /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
AA1C7E3D269A2DD600BAADF2 /* EditTrackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditTrackViewController.swift; sourceTree = "<group>"; };
AC4209FF2D79BCEC00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/InfoPlist.strings; sourceTree = "<group>"; };
AC420A002D79BCED00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/Localizable.strings; sourceTree = "<group>"; };
AC420A012D79BCED00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = af; path = af.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
AC420A022D79BCEE00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/LocalizableTypes.strings; sourceTree = "<group>"; };
AC420A082D79BDDA00A64AA9 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/InfoPlist.strings; sourceTree = "<group>"; };
AC420A092D79BDDA00A64AA9 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = "<group>"; };
AC420A0A2D79BDDB00A64AA9 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = lt; path = lt.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@ -1398,10 +1398,6 @@
AC420A142D79C2EC00A64AA9 /* mt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mt; path = mt.lproj/Localizable.strings; sourceTree = "<group>"; };
AC420A152D79C2EC00A64AA9 /* mt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = mt; path = mt.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
AC420A162D79C2ED00A64AA9 /* mt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mt; path = mt.lproj/LocalizableTypes.strings; sourceTree = "<group>"; };
AC4209FF2D79BCEC00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/InfoPlist.strings; sourceTree = "<group>"; };
AC420A002D79BCED00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/Localizable.strings; sourceTree = "<group>"; };
AC420A012D79BCED00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = af; path = af.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
AC420A022D79BCEE00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/LocalizableTypes.strings; sourceTree = "<group>"; };
AC79C8912A65AB9500594C24 /* UIColor+hexString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+hexString.swift"; sourceTree = "<group>"; };
B33D21AE20DAF9F000BAD749 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = "<group>"; };
B3E3B4FC20D463B700DA8C13 /* BMCCategoriesHeader.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BMCCategoriesHeader.xib; sourceTree = "<group>"; };
@ -1472,12 +1468,8 @@
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>"; };
ED70D57D2D539A2500738C1E /* SearchOnMapPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapPresentationController.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>"; };
@ -3291,12 +3283,8 @@
ED70D5812D539A2500738C1E /* Presentation */ = {
isa = PBXGroup;
children = (
ED70D57B2D539A2500738C1E /* MapPassthroughView.swift */,
ED70D57C2D539A2500738C1E /* ModalScreenPresentationStep.swift */,
ED70D57D2D539A2500738C1E /* SearchOnMapModalPresentationController.swift */,
ED70D57E2D539A2500738C1E /* SearchOnMapModalTransitionManager.swift */,
ED70D57F2D539A2500738C1E /* SideMenuDismissalAnimator.swift */,
ED70D5802D539A2500738C1E /* SideMenuPresentationAnimator.swift */,
ED70D57D2D539A2500738C1E /* SearchOnMapPresentationController.swift */,
);
path = Presentation;
sourceTree = "<group>";
@ -4817,16 +4805,12 @@
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 */,
ED70D5902D539A2500738C1E /* SearchOnMapPresentationController.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 */,

View file

@ -12,8 +12,7 @@ final class SearchOnMapTests: XCTestCase {
override func setUp() {
super.setUp()
searchManager = SearchManagerMock.self
presenter = SearchOnMapPresenter(transitionManager: SearchOnMapModalTransitionManager(),
isRouting: false,
presenter = SearchOnMapPresenter(isRouting: false,
didChangeState: { [weak self] in self?.currentState = $0 })
interactor = SearchOnMapInteractor(presenter: presenter, searchManager: searchManager)
view = SearchOnMapViewMock()
@ -217,11 +216,14 @@ final class SearchOnMapTests: XCTestCase {
// MARK: - Mocks
private class SearchOnMapViewMock: SearchOnMapView {
var viewModel: SearchOnMap.ViewModel = .initial
var scrollViewDelegate: (any SearchOnMapScrollViewDelegate)?
func render(_ viewModel: SearchOnMap.ViewModel) {
self.viewModel = viewModel
}
func close() {
}
}
private class SearchManagerMock: SearchManager {

View file

@ -1,30 +0,0 @@
/// 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

@ -48,10 +48,11 @@ extension ModalScreenPresentationStep {
.compact
}
func frame(for viewController: UIViewController, in containerView: UIView) -> CGRect {
func frame() -> CGRect {
let isIPad = UIDevice.current.userInterfaceIdiom == .pad
let containerSize = containerView.bounds.size
let safeAreaInsets = containerView.safeAreaInsets
let containerSize = UIScreen.main.bounds.size
let safeAreaInsets = UIApplication.shared.keyWindow?.safeAreaInsets ?? .zero
let traitCollection = UIScreen.main.traitCollection
var frame = CGRect(origin: .zero, size: containerSize)
if isIPad {
@ -65,7 +66,7 @@ extension ModalScreenPresentationStep {
return frame
}
let isPortraitOrientation = viewController.traitCollection.verticalSizeClass == .regular
let isPortraitOrientation = traitCollection.verticalSizeClass == .regular
if isPortraitOrientation {
switch self {
case .fullScreen:

View file

@ -1,21 +0,0 @@
@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

@ -2,14 +2,10 @@ protocol ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat)
}
protocol SearchOnMapModalPresentationView: AnyObject {
func setPresentationStep(_ step: ModalScreenPresentationStep)
func close()
}
final class SearchOnMapModalPresentationController: UIPresentationController {
final class SearchOnMapPresentationController: NSObject {
private enum StepChangeAnimation {
case none
case slide
case slideAndBounce
}
@ -26,83 +22,84 @@ final class SearchOnMapModalPresentationController: UIPresentationController {
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 initialTranslationY: CGFloat = .zero
private weak var interactor: SearchOnMapInteractor? { presentedViewController?.interactor }
// TODO: (KK) replace with set of steps passed from the outside
private var presentationStep: ModalScreenPresentationStep = .fullScreen
private var internalScrollViewContentOffset: CGFloat = 0
private var internalScrollViewContentOffset: CGFloat = .zero
private var maxAvailableFrameOfPresentedView: CGRect = .zero
// MARK: - Init
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
private weak var presentedViewController: SearchOnMapViewController?
private weak var parentViewController: UIViewController?
private weak var containerView: UIView?
init(parentViewController: UIViewController,
containerView: UIView) {
self.parentViewController = parentViewController
self.containerView = containerView
}
func setViewController(_ viewController: SearchOnMapViewController) {
self.presentedViewController = viewController
guard let containerView, let parentViewController else { return }
containerView.addSubview(viewController.view)
parentViewController.addChild(viewController)
viewController.view.frame = frameOfPresentedViewInContainerView
viewController.didMove(toParent: parentViewController)
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
}
viewController.view.addGestureRecognizer(panGestureRecognizer)
viewController.scrollViewDelegate = self
}
animateTo(.hidden, animation: .none)
}
func show() {
interactor?.handle(.openSearch)
}
func close() {
guard let presentedViewController else { return }
presentedViewController.willMove(toParent: nil)
animateTo(.hidden) {
presentedViewController.view.removeFromSuperview()
presentedViewController.removeFromParent()
}
}
// 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()
func setPresentationStep(_ step: ModalScreenPresentationStep) {
guard presentationStep != step else { return }
animateTo(step)
}
// MARK: - Layout
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView else { return .zero }
let frame = presentationStep.frame(for: presentedViewController, in: containerView)
func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
presentedViewController?.view.frame = frameOfPresentedViewInContainerView
presentedViewController?.view.layoutIfNeeded()
}
private var frameOfPresentedViewInContainerView: CGRect {
updateMaxAvailableFrameOfPresentedView()
let frame = presentationStep.frame()
return frame
}
private func updateMaxAvailableFrameOfPresentedView() {
guard let containerView else { return }
maxAvailableFrameOfPresentedView = ModalScreenPresentationStep.fullScreen.frame(for: presentedViewController, in: containerView)
maxAvailableFrameOfPresentedView = ModalScreenPresentationStep.fullScreen.frame()
}
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)
}
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 }
guard let presentedViewController, let presentedView = presentedViewController.view else { return }
interactor?.handle(.didStartDraggingSearch)
let translation = gesture.translation(in: presentedView)
@ -114,7 +111,6 @@ final class SearchOnMapModalPresentationController: UIPresentationController {
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
@ -142,15 +138,19 @@ final class SearchOnMapModalPresentationController: UIPresentationController {
}
}
private func animateTo(_ presentationStep: ModalScreenPresentationStep, animation: StepChangeAnimation = .slide) {
guard let presentedView, let containerView else { return }
private func animateTo(_ presentationStep: ModalScreenPresentationStep, animation: StepChangeAnimation = .slide, completion: (() -> Void)? = nil) {
guard let presentedViewController, let presentedView = presentedViewController.view else { return }
self.presentationStep = presentationStep
interactor?.handle(.didUpdatePresentationStep(presentationStep))
let updatedFrame = presentationStep.frame(for: presentedViewController, in: containerView)
let updatedFrame = presentationStep.frame()
let targetYTranslation = updatedFrame.origin.y
switch animation {
case .none:
presentedView.frame = updatedFrame
translationYDidUpdate(targetYTranslation)
completion?()
case .slide:
UIView.animate(withDuration: Constants.animationDuration,
delay: 0,
@ -158,8 +158,9 @@ final class SearchOnMapModalPresentationController: UIPresentationController {
animations: { [weak self] in
presentedView.frame = updatedFrame
self?.translationYDidUpdate(targetYTranslation)
self?.updateSideButtonsAvailableArea(targetYTranslation)
})
}) { _ in
completion?()
}
case .slideAndBounce:
UIView.animate(withDuration: Constants.animationDuration,
delay: 0,
@ -169,37 +170,25 @@ final class SearchOnMapModalPresentationController: UIPresentationController {
animations: { [weak self] in
presentedView.frame = updatedFrame
self?.translationYDidUpdate(targetYTranslation)
self?.updateSideButtonsAvailableArea(targetYTranslation)
})
}) { _ in
completion?()
}
}
}
}
// 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 {
extension SearchOnMapPresentationController: ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat) {
iPhoneSpecific {
(presentedViewController as? SearchOnMapViewController)?.translationYDidUpdate(translationY)
presentedViewController?.translationYDidUpdate(translationY)
updateSideButtonsAvailableArea(translationY)
}
}
}
// MARK: - UIGestureRecognizerDelegate
extension SearchOnMapModalPresentationController: UIGestureRecognizerDelegate {
extension SearchOnMapPresentationController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
true
}
@ -211,9 +200,9 @@ extension SearchOnMapModalPresentationController: UIGestureRecognizerDelegate {
}
// MARK: - SearchOnMapScrollViewDelegate
extension SearchOnMapModalPresentationController: SearchOnMapScrollViewDelegate {
extension SearchOnMapPresentationController: SearchOnMapScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let presentedView else { return }
guard let presentedViewController, let presentedView = presentedViewController.view else { return }
let hasReachedTheTop = Int(presentedView.frame.origin.y) > Int(maxAvailableFrameOfPresentedView.origin.y)
let hasZeroContentOffset = internalScrollViewContentOffset == 0
if hasReachedTheTop && hasZeroContentOffset {

View file

@ -1,22 +0,0 @@
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

@ -1,25 +0,0 @@
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

@ -19,6 +19,7 @@ final class SearchOnMapHeaderView: UIView {
private let grabberView = UIView()
private let searchBar = UISearchBar()
private let cancelButton = UIButton()
private let cancelContainer = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
@ -59,8 +60,8 @@ final class SearchOnMapHeaderView: UIView {
}
private func setupCancelButton() {
cancelButton.tintColor = .whitePrimaryText()
cancelButton.setStyle(.clearBackground)
cancelContainer.setStyle(.primaryBackground)
cancelButton.setStyle(.searchCancelButton)
cancelButton.setTitle(L("cancel"), for: .normal)
cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside)
}
@ -68,10 +69,12 @@ final class SearchOnMapHeaderView: UIView {
private func layoutView() {
addSubview(grabberView)
addSubview(searchBar)
addSubview(cancelButton)
addSubview(cancelContainer)
cancelContainer.addSubview(cancelButton)
grabberView.translatesAutoresizingMaskIntoConstraints = false
searchBar.translatesAutoresizingMaskIntoConstraints = false
cancelContainer.translatesAutoresizingMaskIntoConstraints = false
cancelButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
@ -82,10 +85,16 @@ final class SearchOnMapHeaderView: UIView {
searchBar.topAnchor.constraint(equalTo: grabberView.bottomAnchor),
searchBar.leadingAnchor.constraint(equalTo: leadingAnchor),
searchBar.trailingAnchor.constraint(equalTo: cancelButton.leadingAnchor, constant: -Constants.cancelButtonInsets.left),
searchBar.trailingAnchor.constraint(equalTo: cancelContainer.leadingAnchor),
cancelButton.centerYAnchor.constraint(equalTo: searchBar.centerYAnchor),
cancelButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Constants.cancelButtonInsets.right),
cancelContainer.trailingAnchor.constraint(equalTo: trailingAnchor),
cancelContainer.topAnchor.constraint(equalTo: searchBar.topAnchor),
cancelContainer.bottomAnchor.constraint(equalTo: searchBar.bottomAnchor),
cancelButton.topAnchor.constraint(equalTo: cancelContainer.topAnchor),
cancelButton.leadingAnchor.constraint(equalTo: cancelContainer.leadingAnchor, constant: Constants.cancelButtonInsets.left),
cancelButton.trailingAnchor.constraint(equalTo: cancelContainer.trailingAnchor, constant: -Constants.cancelButtonInsets.right),
cancelButton.bottomAnchor.constraint(equalTo: cancelContainer.bottomAnchor),
bottomAnchor.constraint(equalTo: searchBar.bottomAnchor)
])

View file

@ -70,12 +70,12 @@ final class SearchOnMapInteractor: NSObject {
searchManager.saveQuery(searchText.text,
forInputLocale: searchText.locale)
showResultsOnMap = true
searchManager.showEverywhereSearchResultsOnMap()
return .showOnTheMap
}
private func processTypedText(_ searchText: SearchOnMap.SearchText) -> SearchOnMap.Response {
isUpdatesDisabled = false
showResultsOnMap = true
searchManager.searchQuery(searchText.text,
forInputLocale: searchText.locale,
withCategory: false)

View file

@ -19,16 +19,14 @@ protocol SearchOnMapManagerObserver: AnyObject {
@objcMembers
final class SearchOnMapManager: NSObject {
private let navigationController: UINavigationController
private weak var interactor: SearchOnMapInteractor?
private var interactor: SearchOnMapInteractor? { viewController?.interactor }
private let observers = ListenerContainer<SearchOnMapManagerObserver>()
// MARK: - Public properties
weak var viewController: UIViewController?
weak var viewController: SearchOnMapViewController?
var isSearching: Bool { viewController != nil }
init(navigationController: UINavigationController = MapViewController.shared()!.navigationController!) {
self.navigationController = navigationController
override init() {
super.init()
}
// MARK: - Public methods
@ -38,10 +36,9 @@ final class SearchOnMapManager: NSObject {
return
}
FrameworkHelper.deactivateMapSelection()
let viewController = buildViewController(isRouting: isRouting)
let viewController = SearchOnMapViewControllerBuilder.build(isRouting: isRouting,
didChangeState: notifyObservers)
self.viewController = viewController
self.interactor = viewController.interactor
navigationController.present(viewController, animated: true)
}
func hide() {
@ -77,20 +74,23 @@ final class SearchOnMapManager: NSObject {
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) }
})
private func notifyObservers(_ state: SearchOnMapState) {
observers.forEach { observer in observer.searchManager(didChangeState: state) }
}
}
private struct SearchOnMapViewControllerBuilder {
static func build(isRouting: Bool, didChangeState: @escaping ((SearchOnMapState) -> Void)) -> SearchOnMapViewController {
let mapViewController = MapViewController.shared()!
let presentationController = SearchOnMapPresentationController(parentViewController: mapViewController,
containerView: mapViewController.searchContainer)
let viewController = SearchOnMapViewController(presentationController: presentationController)
let presenter = SearchOnMapPresenter(isRouting: isRouting,
didChangeState: didChangeState)
let interactor = SearchOnMapInteractor(presenter: presenter)
let viewController = SearchOnMapViewController(interactor: interactor)
presenter.view = viewController
viewController.modalPresentationStyle = .custom
viewController.transitioningDelegate = transitioningManager
viewController.interactor = interactor
presentationController.show()
return viewController
}
}

View file

@ -3,7 +3,6 @@ final class SearchOnMapPresenter {
typealias ViewModel = SearchOnMap.ViewModel
weak var view: SearchOnMapView?
weak var presentationView: SearchOnMapModalPresentationView? { transitionManager.presentationController }
private var searchState: SearchOnMapState = .searching {
didSet {
@ -12,13 +11,11 @@ final class SearchOnMapPresenter {
}
}
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
init(isRouting: Bool, didChangeState: ((SearchOnMapState) -> Void)?) {
self.isRouting = isRouting
self.didChangeState = didChangeState
didChangeState?(searchState)
@ -28,8 +25,8 @@ final class SearchOnMapPresenter {
guard response != .none else { return }
if response == .close {
view?.close()
searchState = .closed
presentationView?.close()
return
}
@ -43,7 +40,6 @@ final class SearchOnMapPresenter {
viewModel = newViewModel
view?.render(newViewModel)
searchState = newViewModel.presentationStep.searchState
presentationView?.setPresentationStep(newViewModel.presentationStep)
}
}
@ -97,6 +93,9 @@ final class SearchOnMapPresenter {
viewModel.isTyping = false
viewModel.presentationStep = .compact
case .updatePresentationStep(let step):
if step == .hidden {
viewModel.isTyping = false
}
viewModel.presentationStep = step
case .close, .none:
break

View file

@ -2,6 +2,7 @@ protocol SearchOnMapView: AnyObject {
var scrollViewDelegate: SearchOnMapScrollViewDelegate? { get set }
func render(_ viewModel: SearchOnMap.ViewModel)
func close()
}
@objc
@ -15,16 +16,13 @@ final class SearchOnMapViewController: UIViewController {
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
var interactor: SearchOnMapInteractor?
var modalPresentationController: SearchOnMapPresentationController?
weak var scrollViewDelegate: SearchOnMapScrollViewDelegate?
private var searchResults = SearchOnMap.SearchResults([])
// MARK: - UI Elements
@ -32,21 +30,16 @@ final class SearchOnMapViewController: UIViewController {
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
init(presentationController: SearchOnMapPresentationController?) {
self.modalPresentationController = presentationController
super.init(nibName: nil, bundle: nil)
modalPresentationController?.setViewController(self)
}
@available(*, unavailable)
@ -54,16 +47,12 @@ final class SearchOnMapViewController: UIViewController {
fatalError("init(coder:) has not been implemented")
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
layoutViews()
interactor.handle(.openSearch)
modalPresentationController?.show()
}
override func viewWillDisappear(_ animated: Bool) {
@ -71,6 +60,11 @@ final class SearchOnMapViewController: UIViewController {
headerView.setIsSearching(false)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
modalPresentationController?.traitCollectionDidChange(traitCollection)
}
// MARK: - Private methods
private func setupViews() {
view.setStyle(.clearBackground)
@ -80,7 +74,6 @@ final class SearchOnMapViewController: UIViewController {
setupResultsTableView()
setupHistoryAndCategoryTabView()
setupResultsTableView()
setupFiltersCollectionView()
}
private func setupTapGestureRecognizer() {
@ -112,12 +105,6 @@ final class SearchOnMapViewController: UIViewController {
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)
@ -261,6 +248,16 @@ extension SearchOnMapViewController: SearchOnMapView {
if let searchingText = viewModel.searchingText {
replaceSearchText(with: searchingText)
}
modalPresentationController?.setPresentationStep(viewModel.presentationStep)
}
func close() {
headerView.setIsSearching(false)
guard let modalPresentationController else {
dismiss(animated: true)
return
}
modalPresentationController.close()
}
}
@ -303,12 +300,12 @@ extension SearchOnMapViewController: UITableViewDataSource {
extension SearchOnMapViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let result = searchResults[indexPath.row]
interactor.handle(.didSelectResult(result, withSearchText: headerView.searchText))
interactor?.handle(.didSelectResult(result, withSearchText: headerView.searchText))
tableView.deselectRow(at: indexPath, animated: true)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
interactor.handle(.didStartDraggingSearch)
interactor?.handle(.didStartDraggingSearch)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
@ -332,31 +329,31 @@ extension SearchOnMapViewController: UICollectionViewDataSource {
// MARK: - SearchOnMapHeaderViewDelegate
extension SearchOnMapViewController: SearchOnMapHeaderViewDelegate {
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
interactor.handle(.didStartTyping)
interactor?.handle(.didStartTyping)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
guard !searchText.isEmpty else {
interactor.handle(.clearButtonDidTap)
interactor?.handle(.clearButtonDidTap)
return
}
interactor.handle(.didType(SearchText(searchText, locale: searchBar.textInputMode?.primaryLanguage)))
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)))
interactor?.handle(.searchButtonDidTap(SearchText(searchText, locale: searchBar.textInputMode?.primaryLanguage)))
}
func cancelButtonDidTap() {
interactor.handle(.closeSearch)
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))
interactor?.handle(.didSelectText(SearchText(text, locale: nil), isCategory: withCategory))
}
}