Merge pull request #5640 from igrechuhin/MAPSME-4096

[MAPSME-4096] [ios] Added photos view controller.
This commit is contained in:
Vlad Mihaylenko 2017-03-21 11:21:53 +03:00 committed by GitHub
commit 3d83f7c1f0
14 changed files with 892 additions and 9 deletions

View file

@ -0,0 +1,14 @@
import UIKit
extension UIView {
func center(inContainerView containerView: UIView) -> CGPoint {
guard let sv = superview else { return .zero }
var centerPoint = center
if let scrollView = sv as? UIScrollView , scrollView.zoomScale != 1.0 {
centerPoint.x += (scrollView.bounds.width - scrollView.contentSize.width) / 2.0 + scrollView.contentOffset.x
centerPoint.y += (scrollView.bounds.height - scrollView.contentSize.height) / 2.0 + scrollView.contentOffset.y
}
return sv.convert(centerPoint, to: containerView)
}
}

View file

@ -0,0 +1,23 @@
import UIKit
extension UIView {
var snapshot: UIView {
guard let contents = layer.contents else {
return snapshotView(afterScreenUpdates: true)!
}
let snapshot: UIView
if let view = self as? UIImageView {
snapshot = UIImageView(image: view.image)
snapshot.bounds = view.bounds
} else {
snapshot = UIView(frame: frame)
snapshot.layer.contents = contents
snapshot.layer.bounds = layer.bounds
}
snapshot.layer.cornerRadius = layer.cornerRadius
snapshot.layer.masksToBounds = layer.masksToBounds
snapshot.contentMode = contentMode
snapshot.transform = transform
return snapshot
}
}

View file

@ -21,3 +21,8 @@ func iPhoneSpecific( _ f: () -> Void) {
func toString(_ cls: AnyClass) -> String {
return String(describing: cls)
}
func statusBarHeight() -> CGFloat {
let statusBarSize = UIApplication.shared.statusBarFrame.size
return min(statusBarSize.height, statusBarSize.width)
}

View file

@ -9,6 +9,30 @@
/* Begin PBXBuildFile section */
1D3623260D0F684500981E51 /* MapsAppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* MapsAppDelegate.mm */; };
1D60589B0D05DD56006BFB54 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.mm */; };
3404163B1E7BDFE000E2B6D6 /* PhotosViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3404163A1E7BDFE000E2B6D6 /* PhotosViewController.swift */; };
3404163C1E7BDFE000E2B6D6 /* PhotosViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3404163A1E7BDFE000E2B6D6 /* PhotosViewController.swift */; };
3404163D1E7BDFE000E2B6D6 /* PhotosViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3404163A1E7BDFE000E2B6D6 /* PhotosViewController.swift */; };
340416431E7BED3900E2B6D6 /* PhotosTransitionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340416421E7BED3900E2B6D6 /* PhotosTransitionAnimator.swift */; };
340416441E7BED3900E2B6D6 /* PhotosTransitionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340416421E7BED3900E2B6D6 /* PhotosTransitionAnimator.swift */; };
340416451E7BED3900E2B6D6 /* PhotosTransitionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340416421E7BED3900E2B6D6 /* PhotosTransitionAnimator.swift */; };
340416471E7BF28E00E2B6D6 /* UIView+Snapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340416461E7BF28E00E2B6D6 /* UIView+Snapshot.swift */; };
340416481E7BF28E00E2B6D6 /* UIView+Snapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340416461E7BF28E00E2B6D6 /* UIView+Snapshot.swift */; };
340416491E7BF28E00E2B6D6 /* UIView+Snapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340416461E7BF28E00E2B6D6 /* UIView+Snapshot.swift */; };
3404164B1E7BF42E00E2B6D6 /* UIView+Coordinates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3404164A1E7BF42D00E2B6D6 /* UIView+Coordinates.swift */; };
3404164C1E7BF42E00E2B6D6 /* UIView+Coordinates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3404164A1E7BF42D00E2B6D6 /* UIView+Coordinates.swift */; };
3404164D1E7BF42E00E2B6D6 /* UIView+Coordinates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3404164A1E7BF42D00E2B6D6 /* UIView+Coordinates.swift */; };
3404164F1E7C085F00E2B6D6 /* PhotoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3404164E1E7C085F00E2B6D6 /* PhotoViewController.swift */; };
340416501E7C086000E2B6D6 /* PhotoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3404164E1E7C085F00E2B6D6 /* PhotoViewController.swift */; };
340416511E7C086000E2B6D6 /* PhotoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3404164E1E7C085F00E2B6D6 /* PhotoViewController.swift */; };
340416531E7C09C200E2B6D6 /* PhotoScalingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340416521E7C09C200E2B6D6 /* PhotoScalingView.swift */; };
340416541E7C09C200E2B6D6 /* PhotoScalingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340416521E7C09C200E2B6D6 /* PhotoScalingView.swift */; };
340416551E7C09C200E2B6D6 /* PhotoScalingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340416521E7C09C200E2B6D6 /* PhotoScalingView.swift */; };
340416571E7C0D4100E2B6D6 /* PhotosOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340416561E7C0D4100E2B6D6 /* PhotosOverlayView.swift */; };
340416581E7C0D4100E2B6D6 /* PhotosOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340416561E7C0D4100E2B6D6 /* PhotosOverlayView.swift */; };
340416591E7C0D4100E2B6D6 /* PhotosOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340416561E7C0D4100E2B6D6 /* PhotosOverlayView.swift */; };
3404165B1E7C29AE00E2B6D6 /* PhotosInteractionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3404165A1E7C29AE00E2B6D6 /* PhotosInteractionAnimator.swift */; };
3404165C1E7C29AE00E2B6D6 /* PhotosInteractionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3404165A1E7C29AE00E2B6D6 /* PhotosInteractionAnimator.swift */; };
3404165D1E7C29AE00E2B6D6 /* PhotosInteractionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3404165A1E7C29AE00E2B6D6 /* PhotosInteractionAnimator.swift */; };
340474F01E08199D00C92850 /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 340474DC1E08199D00C92850 /* Crashlytics.framework */; };
340474F11E08199D00C92850 /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 340474DC1E08199D00C92850 /* Crashlytics.framework */; };
340474F21E08199D00C92850 /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 340474DC1E08199D00C92850 /* Crashlytics.framework */; };
@ -1450,6 +1474,14 @@
1D3623250D0F684500981E51 /* MapsAppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = MapsAppDelegate.mm; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
28A0AB4B0D9B1048005BE974 /* Maps_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Maps_Prefix.pch; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
29B97316FDCFA39411CA2CEA /* main.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = main.mm; sourceTree = "<group>"; };
3404163A1E7BDFE000E2B6D6 /* PhotosViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotosViewController.swift; sourceTree = "<group>"; };
340416421E7BED3900E2B6D6 /* PhotosTransitionAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotosTransitionAnimator.swift; sourceTree = "<group>"; };
340416461E7BF28E00E2B6D6 /* UIView+Snapshot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Snapshot.swift"; sourceTree = "<group>"; };
3404164A1E7BF42D00E2B6D6 /* UIView+Coordinates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Coordinates.swift"; sourceTree = "<group>"; };
3404164E1E7C085F00E2B6D6 /* PhotoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoViewController.swift; sourceTree = "<group>"; };
340416521E7C09C200E2B6D6 /* PhotoScalingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoScalingView.swift; sourceTree = "<group>"; };
340416561E7C0D4100E2B6D6 /* PhotosOverlayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotosOverlayView.swift; sourceTree = "<group>"; };
3404165A1E7C29AE00E2B6D6 /* PhotosInteractionAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotosInteractionAnimator.swift; sourceTree = "<group>"; };
340474DC1E08199D00C92850 /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = "<group>"; };
340474DD1E08199D00C92850 /* Fabric.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Fabric.framework; sourceTree = "<group>"; };
340474DE1E08199D00C92850 /* FBSDKCoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = FBSDKCoreKit.framework; sourceTree = "<group>"; };
@ -2504,6 +2536,19 @@
name = Frameworks;
sourceTree = "<group>";
};
340416391E7BDFB800E2B6D6 /* Photos */ = {
isa = PBXGroup;
children = (
340416521E7C09C200E2B6D6 /* PhotoScalingView.swift */,
3404165A1E7C29AE00E2B6D6 /* PhotosInteractionAnimator.swift */,
340416561E7C0D4100E2B6D6 /* PhotosOverlayView.swift */,
340416421E7BED3900E2B6D6 /* PhotosTransitionAnimator.swift */,
3404163A1E7BDFE000E2B6D6 /* PhotosViewController.swift */,
3404164E1E7C085F00E2B6D6 /* PhotoViewController.swift */,
);
path = Photos;
sourceTree = "<group>";
};
340474DB1E08199D00C92850 /* 3party */ = {
isa = PBXGroup;
children = (
@ -2761,6 +2806,8 @@
349D1CE21E3F836900A878FD /* UIViewController+Hierarchy.swift */,
34F7422F1E0834F400AC1FD6 /* UIViewController+Navigation.h */,
34F742301E0834F400AC1FD6 /* UIViewController+Navigation.mm */,
340416461E7BF28E00E2B6D6 /* UIView+Snapshot.swift */,
3404164A1E7BF42D00E2B6D6 /* UIView+Coordinates.swift */,
);
path = Categories;
sourceTree = "<group>";
@ -2845,6 +2892,7 @@
346DB81C1E5C4F6700E3123E /* Gallery */ = {
isa = PBXGroup;
children = (
340416391E7BDFB800E2B6D6 /* Photos */,
346DB81D1E5C4F6700E3123E /* Cells */,
346DB8201E5C4F6700E3123E /* GalleryItemViewController.swift */,
346DB8211E5C4F6700E3123E /* GalleryItemViewController.xib */,
@ -4869,6 +4917,7 @@
F6E2FF081E097BA00083EBEC /* MWMSearchHistoryManager.mm in Sources */,
F6E2FD8E1E097BA00083EBEC /* MWMNoMapsViewController.mm in Sources */,
34D3B0411E389D05004100F9 /* MWMEditorTextTableViewCell.mm in Sources */,
3404163B1E7BDFE000E2B6D6 /* PhotosViewController.swift in Sources */,
F6E2FE601E097BA00083EBEC /* MWMBookmarkCell.mm in Sources */,
F6E2FEA21E097BA00083EBEC /* MWMPPView.mm in Sources */,
F6791B131C43DEA7007A8A6E /* MWMStartButton.mm in Sources */,
@ -4933,6 +4982,7 @@
F607C18E1C047FDC00B53A87 /* MWMSegue.mm in Sources */,
F6E2FE301E097BA00083EBEC /* MWMStreetEditorViewController.mm in Sources */,
F6E2FE3F1E097BA00083EBEC /* MWMPlacePageEntity.mm in Sources */,
340416471E7BF28E00E2B6D6 /* UIView+Snapshot.swift in Sources */,
F6E2FE271E097BA00083EBEC /* MWMOpeningHoursSection.mm in Sources */,
F6E2FE241E097BA00083EBEC /* MWMOpeningHoursModel.mm in Sources */,
F6BD33811B62403B00F2CE18 /* MWMRoutePreview.mm in Sources */,
@ -5000,6 +5050,7 @@
F6E2FEC91E097BA00083EBEC /* MWMSearchFilterTransitioning.mm in Sources */,
FA054612155C465E001F4E37 /* SelectSetVC.mm in Sources */,
F6E2FE811E097BA00083EBEC /* MWMPlacePageOpeningHoursDayView.mm in Sources */,
340416571E7C0D4100E2B6D6 /* PhotosOverlayView.swift in Sources */,
F6E2FD6A1E097BA00083EBEC /* MWMMapDownloaderSubplaceTableViewCell.mm in Sources */,
FAA614B8155F16950031C345 /* AddSetVC.mm in Sources */,
F6E2FF681E097BA00083EBEC /* MWMUnitsController.mm in Sources */,
@ -5037,12 +5088,14 @@
F6E2FE9F1E097BA00083EBEC /* MWMPlacePageLayout.mm in Sources */,
F6E2FEC31E097BA00083EBEC /* MWMSearchFilterPresentationController.mm in Sources */,
340475731E081A4600C92850 /* MWMStorage.mm in Sources */,
340416531E7C09C200E2B6D6 /* PhotoScalingView.swift in Sources */,
F6E2FD851E097BA00083EBEC /* MWMBaseMapDownloaderViewController.mm in Sources */,
34ABA6241C2D551900FE1BEC /* MWMInputValidatorFactory.mm in Sources */,
34943BBA1E2626B200B14F84 /* WelcomePageController.swift in Sources */,
F6E2FEE11E097BA00083EBEC /* MWMSearchManager.mm in Sources */,
34D3AFE11E376F7E004100F9 /* UITableView+Updates.swift in Sources */,
F6E2FE211E097BA00083EBEC /* MWMOpeningHoursEditorViewController.mm in Sources */,
3404164B1E7BF42E00E2B6D6 /* UIView+Coordinates.swift in Sources */,
349D1ADA1E2E325C004A2006 /* MWMBottomMenuView.mm in Sources */,
F6E2FD911E097BA00083EBEC /* MWMBookmarkColorViewController.mm in Sources */,
F6F7787A1DABC6D800B603E7 /* MWMTaxiCollectionLayout.mm in Sources */,
@ -5051,6 +5104,7 @@
34D3B0291E389D05004100F9 /* MWMEditorAdditionalNameTableViewCell.mm in Sources */,
34D4FA621E26572D003F53EF /* FirstLaunchController.swift in Sources */,
34C9BD041C6DB693000DC38D /* MWMViewController.mm in Sources */,
3404165B1E7C29AE00E2B6D6 /* PhotosInteractionAnimator.swift in Sources */,
340475701E081A4600C92850 /* MWMSettings.mm in Sources */,
3404756D1E081A4600C92850 /* MWMSearch.mm in Sources */,
3486B5071E27A4B50069C126 /* LocalNotificationManager.mm in Sources */,
@ -5066,6 +5120,7 @@
F6E2FF021E097BA00083EBEC /* MWMSearchHistoryClearCell.mm in Sources */,
F63774EA1B59376F00BCF54D /* MWMRoutingDisclaimerAlert.mm in Sources */,
340475081E08199E00C92850 /* MWMMyTarget.mm in Sources */,
3404164F1E7C085F00E2B6D6 /* PhotoViewController.swift in Sources */,
F64F19A31AB81A00006EAF7E /* MWMDownloadTransitMapAlert.mm in Sources */,
3404754C1E081A4600C92850 /* MWMKeyboard.mm in Sources */,
F6BD33841B6240F200F2CE18 /* MWMNavigationView.mm in Sources */,
@ -5083,6 +5138,7 @@
ED48BBB517C267F5003E7E92 /* ColorPickerView.mm in Sources */,
34F5E0D71E3F334700B1C415 /* Types.swift in Sources */,
F6E2FF561E097BA00083EBEC /* MWMMobileInternetViewController.mm in Sources */,
340416431E7BED3900E2B6D6 /* PhotosTransitionAnimator.swift in Sources */,
ED48BBBA17C2B1E2003E7E92 /* CircleView.mm in Sources */,
F6E2FEEA1E097BA00083EBEC /* MWMSearchTextField.mm in Sources */,
F6664C121E645A4100E703C2 /* MWMPPReviewCell.mm in Sources */,
@ -5141,6 +5197,7 @@
F6E2FF091E097BA00083EBEC /* MWMSearchHistoryManager.mm in Sources */,
F6E2FD8F1E097BA00083EBEC /* MWMNoMapsViewController.mm in Sources */,
34D3B0421E389D05004100F9 /* MWMEditorTextTableViewCell.mm in Sources */,
3404163C1E7BDFE000E2B6D6 /* PhotosViewController.swift in Sources */,
F6E2FE611E097BA00083EBEC /* MWMBookmarkCell.mm in Sources */,
F6E2FEA31E097BA00083EBEC /* MWMPPView.mm in Sources */,
3454D7D11E07F045004AF2AD /* UIImage+RGBAData.mm in Sources */,
@ -5205,6 +5262,7 @@
F6E2FE401E097BA00083EBEC /* MWMPlacePageEntity.mm in Sources */,
F6E2FE281E097BA00083EBEC /* MWMOpeningHoursSection.mm in Sources */,
3406FA161C6E0C3300E9FAD2 /* MWMMapDownloadDialog.mm in Sources */,
340416481E7BF28E00E2B6D6 /* UIView+Snapshot.swift in Sources */,
F6E2FE251E097BA00083EBEC /* MWMOpeningHoursModel.mm in Sources */,
34C9BD031C6DB693000DC38D /* MWMTableViewController.mm in Sources */,
F6E2FD8C1E097BA00083EBEC /* MWMNoMapsView.mm in Sources */,
@ -5272,6 +5330,7 @@
F682249B1E5B104600BC1C18 /* PPHotelDescriptionCell.swift in Sources */,
F6E2FECA1E097BA00083EBEC /* MWMSearchFilterTransitioning.mm in Sources */,
3454D7DD1E07F045004AF2AD /* UISwitch+RuntimeAttributes.m in Sources */,
340416581E7C0D4100E2B6D6 /* PhotosOverlayView.swift in Sources */,
F6E2FE821E097BA00083EBEC /* MWMPlacePageOpeningHoursDayView.mm in Sources */,
F6E2FD6B1E097BA00083EBEC /* MWMMapDownloaderSubplaceTableViewCell.mm in Sources */,
6741AA021BF340DE002C974C /* BookmarksRootVC.mm in Sources */,
@ -5309,12 +5368,14 @@
F6E2FF301E097BA00083EBEC /* MWMSearchCommonCell.mm in Sources */,
F6E2FEA01E097BA00083EBEC /* MWMPlacePageLayout.mm in Sources */,
F6E2FEC41E097BA00083EBEC /* MWMSearchFilterPresentationController.mm in Sources */,
340416541E7C09C200E2B6D6 /* PhotoScalingView.swift in Sources */,
34ABA6251C2D551900FE1BEC /* MWMInputValidatorFactory.mm in Sources */,
F6E2FD861E097BA00083EBEC /* MWMBaseMapDownloaderViewController.mm in Sources */,
F6E2FEE21E097BA00083EBEC /* MWMSearchManager.mm in Sources */,
F6E2FE221E097BA00083EBEC /* MWMOpeningHoursEditorViewController.mm in Sources */,
34943BBB1E2626B200B14F84 /* WelcomePageController.swift in Sources */,
34D3AFE21E376F7E004100F9 /* UITableView+Updates.swift in Sources */,
3404164C1E7BF42E00E2B6D6 /* UIView+Coordinates.swift in Sources */,
6741AA141BF340DE002C974C /* MWMMultilineLabel.mm in Sources */,
349D1ADB1E2E325C004A2006 /* MWMBottomMenuView.mm in Sources */,
F6E2FD921E097BA00083EBEC /* MWMBookmarkColorViewController.mm in Sources */,
@ -5323,6 +5384,7 @@
3454D7CB1E07F045004AF2AD /* UIColor+MapsMeColor.mm in Sources */,
34D3B02A1E389D05004100F9 /* MWMEditorAdditionalNameTableViewCell.mm in Sources */,
340475711E081A4600C92850 /* MWMSettings.mm in Sources */,
3404165C1E7C29AE00E2B6D6 /* PhotosInteractionAnimator.swift in Sources */,
34D4FA631E26572D003F53EF /* FirstLaunchController.swift in Sources */,
3404756E1E081A4600C92850 /* MWMSearch.mm in Sources */,
6741AA191BF340DE002C974C /* MWMDownloaderDialogCell.mm in Sources */,
@ -5338,6 +5400,7 @@
6741AA221BF340DE002C974C /* MWMNavigationView.mm in Sources */,
F6E2FF031E097BA00083EBEC /* MWMSearchHistoryClearCell.mm in Sources */,
340475091E08199E00C92850 /* MWMMyTarget.mm in Sources */,
340416501E7C086000E2B6D6 /* PhotoViewController.swift in Sources */,
674A7E301C0DB10B003D48E1 /* MWMMapWidgets.mm in Sources */,
3404754D1E081A4600C92850 /* MWMKeyboard.mm in Sources */,
34EF94291C05A6F30050B714 /* MWMSegue.mm in Sources */,
@ -5355,6 +5418,7 @@
6741AA281BF340DE002C974C /* MWMAlert.mm in Sources */,
F6E2FF571E097BA00083EBEC /* MWMMobileInternetViewController.mm in Sources */,
34F5E0D81E3F334700B1C415 /* Types.swift in Sources */,
340416441E7BED3900E2B6D6 /* PhotosTransitionAnimator.swift in Sources */,
6741AA291BF340DE002C974C /* ColorPickerView.mm in Sources */,
6741AA2B1BF340DE002C974C /* CircleView.mm in Sources */,
F6E2FEEB1E097BA00083EBEC /* MWMSearchTextField.mm in Sources */,
@ -5413,6 +5477,7 @@
F6E2FF0A1E097BA00083EBEC /* MWMSearchHistoryManager.mm in Sources */,
F6E2FD901E097BA00083EBEC /* MWMNoMapsViewController.mm in Sources */,
34D3B0431E389D05004100F9 /* MWMEditorTextTableViewCell.mm in Sources */,
3404163D1E7BDFE000E2B6D6 /* PhotosViewController.swift in Sources */,
F6E2FE621E097BA00083EBEC /* MWMBookmarkCell.mm in Sources */,
F6E2FEA41E097BA00083EBEC /* MWMPPView.mm in Sources */,
849CF69F1DE842290024A8A5 /* MWMStartButton.mm in Sources */,
@ -5477,6 +5542,7 @@
3404756C1E081A4600C92850 /* MWMSearch+CoreSpotlight.mm in Sources */,
F6E2FE321E097BA00083EBEC /* MWMStreetEditorViewController.mm in Sources */,
F6E2FE411E097BA00083EBEC /* MWMPlacePageEntity.mm in Sources */,
340416491E7BF28E00E2B6D6 /* UIView+Snapshot.swift in Sources */,
F6E2FE291E097BA00083EBEC /* MWMOpeningHoursSection.mm in Sources */,
849CF6D31DE842290024A8A5 /* MWMMapDownloadDialog.mm in Sources */,
F6E2FE261E097BA00083EBEC /* MWMOpeningHoursModel.mm in Sources */,
@ -5544,6 +5610,7 @@
F6E2FECB1E097BA00083EBEC /* MWMSearchFilterTransitioning.mm in Sources */,
849CF70D1DE842290024A8A5 /* MWMAddPlaceNavigationBar.mm in Sources */,
F6E2FE831E097BA00083EBEC /* MWMPlacePageOpeningHoursDayView.mm in Sources */,
340416591E7C0D4100E2B6D6 /* PhotosOverlayView.swift in Sources */,
F6E2FD6C1E097BA00083EBEC /* MWMMapDownloaderSubplaceTableViewCell.mm in Sources */,
849CF70F1DE842290024A8A5 /* MWMNavigationInfoView.mm in Sources */,
F6E2FF6A1E097BA00083EBEC /* MWMUnitsController.mm in Sources */,
@ -5581,12 +5648,14 @@
F6E2FEA11E097BA00083EBEC /* MWMPlacePageLayout.mm in Sources */,
F6E2FEC51E097BA00083EBEC /* MWMSearchFilterPresentationController.mm in Sources */,
F6E2FD871E097BA00083EBEC /* MWMBaseMapDownloaderViewController.mm in Sources */,
340416551E7C09C200E2B6D6 /* PhotoScalingView.swift in Sources */,
F6E2FEE31E097BA00083EBEC /* MWMSearchManager.mm in Sources */,
34943BBC1E2626B200B14F84 /* WelcomePageController.swift in Sources */,
F6E2FE231E097BA00083EBEC /* MWMOpeningHoursEditorViewController.mm in Sources */,
34D3AFE31E376F7E004100F9 /* UITableView+Updates.swift in Sources */,
849CF72F1DE842290024A8A5 /* MWMInputValidator.mm in Sources */,
349D1ADC1E2E325C004A2006 /* MWMBottomMenuView.mm in Sources */,
3404164D1E7BF42E00E2B6D6 /* UIView+Coordinates.swift in Sources */,
F6E2FD931E097BA00083EBEC /* MWMBookmarkColorViewController.mm in Sources */,
849CF7331DE842290024A8A5 /* MWMInputValidatorFactory.mm in Sources */,
F6E2FDA51E097BA00083EBEC /* MWMCuisineEditorViewController.mm in Sources */,
@ -5595,6 +5664,7 @@
34D3B02B1E389D05004100F9 /* MWMEditorAdditionalNameTableViewCell.mm in Sources */,
849CF7371DE842290024A8A5 /* MWMTaxiCollectionLayout.mm in Sources */,
3454D7C61E07F045004AF2AD /* UIButton+Orientation.mm in Sources */,
3404165D1E7C29AE00E2B6D6 /* PhotosInteractionAnimator.swift in Sources */,
340475571E081A4600C92850 /* Statistics.mm in Sources */,
3486B5091E27A4B50069C126 /* LocalNotificationManager.mm in Sources */,
F6E2FEFE1E097BA00083EBEC /* MWMSearchCategoryCell.mm in Sources */,
@ -5610,6 +5680,7 @@
849CF74C1DE842290024A8A5 /* MWMLocationNotFoundAlert.mm in Sources */,
F6E2FF041E097BA00083EBEC /* MWMSearchHistoryClearCell.mm in Sources */,
340475541E081A4600C92850 /* MWMCustomFacebookEvents.mm in Sources */,
340416511E7C086000E2B6D6 /* PhotoViewController.swift in Sources */,
3404750A1E08199E00C92850 /* MWMMyTarget.mm in Sources */,
849CF7561DE842290024A8A5 /* MWMRoutingDisclaimerAlert.mm in Sources */,
849CF7581DE842290024A8A5 /* MWMDownloadTransitMapAlert.mm in Sources */,
@ -5627,6 +5698,7 @@
849CF7631DE842290024A8A5 /* MWMAlert.mm in Sources */,
34F5E0D91E3F334700B1C415 /* Types.swift in Sources */,
F6E2FF581E097BA00083EBEC /* MWMMobileInternetViewController.mm in Sources */,
340416451E7BED3900E2B6D6 /* PhotosTransitionAnimator.swift in Sources */,
849CF7651DE842290024A8A5 /* ColorPickerView.mm in Sources */,
F6E2FEEC1E097BA00083EBEC /* MWMSearchTextField.mm in Sources */,
F6664C141E645A4100E703C2 /* MWMPPReviewCell.mm in Sources */,

View file

@ -1,3 +1,5 @@
typedef UIView * (^MWMPlacePageButtonsDismissBlock)(NSInteger);
@protocol MWMPlacePageButtonsProtocol<NSObject>
- (void)editPlace;
@ -8,7 +10,9 @@
- (void)taxiTo;
- (void)showAllReviews;
- (void)showAllFacilities;
- (void)showPhotoAtIndex:(NSUInteger)index;
- (void)showPhotoAtIndex:(NSInteger)index
referenceView:(UIView *)referenceView
referenceViewWhenDismissingHandler:(MWMPlacePageButtonsDismissBlock)referenceViewWhenDismissingHandler;
- (void)showGalery;
@end

View file

@ -311,19 +311,28 @@ void logSponsoredEvent(MWMPlacePageData * data, NSString * eventName)
[[MapViewController controller] openUrl:self.data.URLToAllReviews];
}
- (void)showPhotoAtIndex:(NSUInteger)index
- (void)showPhotoAtIndex:(NSInteger)index
referenceView:(UIView *)referenceView
referenceViewWhenDismissingHandler:(MWMPlacePageButtonsDismissBlock)referenceViewWhenDismissingHandler
{
logSponsoredEvent(self.data, kPlacePageHotelGallery);
auto model = self.data.photos[index];
auto galleryVc = [MWMGalleryItemViewController instanceWithModel:model];
[[MapViewController controller].navigationController pushViewController:galleryVc animated:YES];
auto galleryModel = [[MWMGalleryModel alloc] initWithTitle:self.hotelName items:self.data.photos];
auto initialPhoto = galleryModel.items[index];
auto photoVC = [[MWMPhotosViewController alloc] initWithPhotos:galleryModel
initialPhoto:initialPhoto
referenceView:referenceView];
photoVC.referenceViewForPhotoWhenDismissingHandler = ^UIView *(MWMGalleryItemModel * photo) {
return referenceViewWhenDismissingHandler([galleryModel.items indexOfObject:photo]);
};
[[MapViewController controller] presentViewController:photoVC animated:YES completion:nil];
}
- (void)showGalery
{
logSponsoredEvent(self.data, kPlacePageHotelGallery);
auto galleryVc = [MWMGalleryViewController instanceWithModel:[[MWMGalleryModel alloc]
initWithTitle:self.hotelName items:self.data.photos]];
auto galleryModel = [[MWMGalleryModel alloc] initWithTitle:self.hotelName items:self.data.photos];
auto galleryVc = [MWMGalleryViewController instanceWithModel:galleryModel];
[[MapViewController controller].navigationController pushViewController:galleryVc animated:YES];
}

View file

@ -54,7 +54,12 @@ extension PPHotelCarouselCell: UICollectionViewDelegate, UICollectionViewDataSou
if isLastCell(indexPath) {
d.showGalery()
} else {
d.showPhoto(at: UInt(indexPath.row))
let section = indexPath.section
d.showPhoto(at: indexPath.item,
referenceView: collectionView.cellForItem(at: indexPath), referenceViewWhenDismissingHandler: { index -> UIView? in
let indexPath = IndexPath(item: index, section: section)
return collectionView.cellForItem(at: indexPath)
})
}
}
}

View file

@ -63,6 +63,17 @@ final class GalleryViewController: MWMCollectionViewController {
// MARK: UICollectionViewDelegate
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
show(GalleryItemViewController.instance(model: model.items[indexPath.item]), sender: nil)
let currentPhoto = model.items[indexPath.item]
let cell = collectionView.cellForItem(at: indexPath)
let photoVC = PhotosViewController(photos: model, initialPhoto: currentPhoto, referenceView: cell)
photoVC.referenceViewForPhotoWhenDismissingHandler = { [weak self] photo in
if let index = self?.model.items.index(where: {$0 === photo}) {
let indexPath = IndexPath(item: index, section: 0)
return collectionView.cellForItem(at: indexPath)
}
return nil
}
present(photoVC, animated: true, completion: nil)
}
}

View file

@ -0,0 +1,81 @@
import AlamofireImage
import UIKit
final class PhotoScalingView: UIScrollView {
private(set) lazy var imageView: UIImageView = {
let imageView = UIImageView(frame: self.bounds)
self.addSubview(imageView)
return imageView
}()
var photo: GalleryItemModel? {
didSet {
updateImage(photo)
}
}
override var frame: CGRect {
get { return super.frame }
set {
super.frame = newValue
updateZoomScale()
centerScrollViewContents()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupImageScrollView()
updateZoomScale()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupImageScrollView()
updateZoomScale()
}
override func didAddSubview(_ subview: UIView) {
super.didAddSubview(subview)
centerScrollViewContents()
}
private func setupImageScrollView() {
showsVerticalScrollIndicator = false
showsHorizontalScrollIndicator = false
bouncesZoom = true
decelerationRate = UIScrollViewDecelerationRateFast
}
private func centerScrollViewContents() {
let horizontalInset: CGFloat = contentSize.width < bounds.width ? (bounds.width - contentSize.width) * 0.5 : 0
let verticalInset: CGFloat = contentSize.height < bounds.height ? (bounds.height - contentSize.height) * 0.5 : 0
contentInset = UIEdgeInsetsMake(verticalInset, horizontalInset, verticalInset, horizontalInset)
}
private func updateImage(_ photo: GalleryItemModel?) {
guard let photo = photo else { return }
imageView.transform = CGAffineTransform.identity
imageView.af_setImage(withURL: photo.imageURL,
imageTransition: .crossDissolve(kDefaultAnimationDuration),
completion: { [weak self] response in
guard let s = self else { return }
s.contentSize = response.value?.size ?? CGSize.zero
s.imageView.frame = CGRect(origin: CGPoint.zero, size: s.contentSize)
s.updateZoomScale()
s.centerScrollViewContents()
})
}
private func updateZoomScale() {
guard let image = imageView.image else { return }
let selfSize = bounds.size
let imageSize = image.size
let wScale = selfSize.width / imageSize.width
let hScale = selfSize.height / imageSize.height
minimumZoomScale = min(wScale, hScale)
maximumZoomScale = max(max(wScale, hScale), maximumZoomScale)
zoomScale = minimumZoomScale
panGestureRecognizer.isEnabled = false
}
}

View file

@ -0,0 +1,76 @@
import UIKit
final class PhotoViewController: UIViewController {
let scalingView = PhotoScalingView()
let photo: GalleryItemModel
private(set) lazy var doubleTapGestureRecognizer: UITapGestureRecognizer = {
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTapWithGestureRecognizer(_:)))
gesture.numberOfTapsRequired = 2
return gesture
}()
init(photo: GalleryItemModel) {
self.photo = photo
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
scalingView.delegate = self
scalingView.frame = view.bounds
scalingView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(scalingView)
view.addGestureRecognizer(doubleTapGestureRecognizer)
scalingView.photo = photo
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
scalingView.frame = view.bounds
}
@objc
private func handleDoubleTapWithGestureRecognizer(_ recognizer: UITapGestureRecognizer) {
let pointInView = recognizer.location(in: scalingView.imageView)
var newZoomScale = scalingView.maximumZoomScale
if scalingView.zoomScale >= scalingView.maximumZoomScale ||
abs(scalingView.zoomScale - scalingView.maximumZoomScale) <= 0.01 {
newZoomScale = scalingView.minimumZoomScale
}
let scrollViewSize = scalingView.bounds.size
let width = scrollViewSize.width / newZoomScale
let height = scrollViewSize.height / newZoomScale
let originX = pointInView.x - (width / 2.0)
let originY = pointInView.y - (height / 2.0)
let rectToZoom = CGRect(x: originX, y: originY, width: width, height: height)
scalingView.zoom(to: rectToZoom, animated: true)
}
}
extension PhotoViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return scalingView.imageView
}
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
scrollView.panGestureRecognizer.isEnabled = true
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
if (scrollView.zoomScale == scrollView.minimumZoomScale) {
scrollView.panGestureRecognizer.isEnabled = false
}
}
}

View file

@ -0,0 +1,129 @@
import UIKit
final class PhotosInteractionAnimator: NSObject {
private enum Settings {
static let returnToCenterVelocityAnimationRatio: CGFloat = 0.00007
static let panDismissDistanceRatio: CGFloat = 0.075
static let panDismissMaximumDuration: TimeInterval = 0.45
}
var animator: UIViewControllerAnimatedTransitioning?
var viewToHideWhenBeginningTransition: UIView?
var shouldAnimateUsingAnimator = false
fileprivate var transitionContext: UIViewControllerContextTransitioning?
func handlePanWithPanGestureRecognizer(_ gestureRecognizer: UIPanGestureRecognizer, viewToPan: UIView, anchorPoint: CGPoint) {
guard let fromView = transitionContext?.view(forKey: UITransitionContextViewKey.from) else {
return
}
let translatedPanGesturePoint = gestureRecognizer.translation(in: fromView)
let newCenterPoint = CGPoint(x: anchorPoint.x, y: anchorPoint.y + translatedPanGesturePoint.y)
viewToPan.center = newCenterPoint
let verticalDelta = newCenterPoint.y - anchorPoint.y
let backgroundAlpha = backgroundAlphaForPanningWithVerticalDelta(verticalDelta)
fromView.backgroundColor = fromView.backgroundColor?.withAlphaComponent(backgroundAlpha)
if gestureRecognizer.state == .ended, let transitionContext = transitionContext, let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) {
let velocityY = gestureRecognizer.velocity(in: gestureRecognizer.view).y
finishPanWith(transitionContext: transitionContext, velocityY: velocityY, verticalDelta: verticalDelta, viewToPan: viewToPan, fromView: fromView, anchorPoint: anchorPoint)
}
}
private func finishPanWith(transitionContext: UIViewControllerContextTransitioning, velocityY: CGFloat, verticalDelta: CGFloat, viewToPan: UIView, fromView: UIView, anchorPoint: CGPoint) {
let dismissDistance = Settings.panDismissDistanceRatio * fromView.bounds.height
let isDismissing = abs(verticalDelta) > dismissDistance
if isDismissing, shouldAnimateUsingAnimator, let animator = animator {
animator.animateTransition(using: transitionContext)
self.transitionContext = nil
return
}
let finalPageViewCenterPoint: CGPoint
let animationDuration: TimeInterval
let finalBackgroundAlpha: CGFloat
if isDismissing {
let modifier: CGFloat = verticalDelta.sign == .plus ? 1 : -1
let finalCenterY = fromView.bounds.midY + modifier * fromView.bounds.height
finalPageViewCenterPoint = CGPoint(x: fromView.center.x, y: finalCenterY)
let duration = TimeInterval(abs(finalPageViewCenterPoint.y - viewToPan.center.y) / abs(velocityY))
animationDuration = min(duration, Settings.panDismissMaximumDuration)
finalBackgroundAlpha = 0.0
} else {
finalPageViewCenterPoint = anchorPoint
animationDuration = TimeInterval(abs(velocityY) * Settings.returnToCenterVelocityAnimationRatio) + kDefaultAnimationDuration
finalBackgroundAlpha = 1.0
}
let finalBackgroundColor = fromView.backgroundColor?.withAlphaComponent(finalBackgroundAlpha)
finishPanWithoutAnimator(duration: animationDuration,
viewToPan: viewToPan, fromView: fromView,
finalPageViewCenterPoint: finalPageViewCenterPoint, finalBackgroundColor: finalBackgroundColor,
isDismissing: isDismissing)
}
private func finishPanWithoutAnimator(duration: TimeInterval, viewToPan: UIView, fromView: UIView, finalPageViewCenterPoint: CGPoint, finalBackgroundColor: UIColor?, isDismissing: Bool) {
UIView.animate(withDuration: duration,
delay: 0,
options: .curveEaseOut,
animations: {
viewToPan.center = finalPageViewCenterPoint
fromView.backgroundColor = finalBackgroundColor
},
completion: { [weak self] _ in
guard let s = self else { return }
if isDismissing {
s.transitionContext?.finishInteractiveTransition()
} else {
s.transitionContext?.cancelInteractiveTransition()
if !s.isRadar20070670Fixed() {
s.fixCancellationStatusBarAppearanceBug()
}
}
s.viewToHideWhenBeginningTransition?.alpha = 1.0
s.transitionContext?.completeTransition(isDismissing && !(s.transitionContext?.transitionWasCancelled ?? false))
s.transitionContext = nil
})
}
private func fixCancellationStatusBarAppearanceBug() {
guard let toViewController = self.transitionContext?.viewController(forKey: UITransitionContextViewControllerKey.to),
let fromViewController = self.transitionContext?.viewController(forKey: UITransitionContextViewControllerKey.from) else {
return
}
let statusBarViewControllerSelector = Selector("_setPresentedSta" + "tusBarViewController:")
if toViewController.responds(to: statusBarViewControllerSelector) && fromViewController.modalPresentationCapturesStatusBarAppearance {
toViewController.perform(statusBarViewControllerSelector, with: fromViewController)
}
}
private func isRadar20070670Fixed() -> Bool {
return ProcessInfo.processInfo.isOperatingSystemAtLeast(OperatingSystemVersion.init(majorVersion: 8, minorVersion: 3, patchVersion: 0))
}
private func backgroundAlphaForPanningWithVerticalDelta(_ delta: CGFloat) -> CGFloat {
guard let fromView = transitionContext?.view(forKey: UITransitionContextViewKey.from) else {
return 0.0
}
let startingAlpha: CGFloat = 1.0
let finalAlpha: CGFloat = 0.1
let totalAvailableAlpha = startingAlpha - finalAlpha
let maximumDelta = CGFloat(fromView.bounds.height / 2.0)
let deltaAsPercentageOfMaximum = min(abs(delta) / maximumDelta, 1.0)
return startingAlpha - (deltaAsPercentageOfMaximum * totalAvailableAlpha)
}
}
extension PhotosInteractionAnimator: UIViewControllerInteractiveTransitioning {
func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
viewToHideWhenBeginningTransition?.alpha = 0.0
self.transitionContext = transitionContext
}
}

View file

@ -0,0 +1,86 @@
import UIKit
final class PhotosOverlayView: UIView {
private var navigationBar: UINavigationBar!
private var navigationItem: UINavigationItem!
weak var photosViewController: PhotosViewController?
var photo: GalleryItemModel? {
didSet {
guard let photo = photo else {
navigationItem.title = nil
return
}
guard let photosViewController = photosViewController else { return }
if let index = photosViewController.photos.items.index(where: { $0 === photo }) {
navigationItem.title = "\(index + 1) / \(photosViewController.photos.items.count)"
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupNavigationBar()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc
private func closeButtonTapped(_ sender: UIBarButtonItem) {
photosViewController?.dismiss(animated: true, completion: nil)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let hitView = super.hitTest(point, with: event) , hitView != self {
return hitView
}
return nil
}
private func setupNavigationBar() {
navigationBar = UINavigationBar()
navigationBar.translatesAutoresizingMaskIntoConstraints = false
navigationBar.backgroundColor = UIColor.clear
navigationBar.barTintColor = nil
navigationBar.isTranslucent = true
navigationBar.shadowImage = UIImage()
navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationItem = UINavigationItem(title: "")
navigationBar.items = [navigationItem]
addSubview(navigationBar)
let topConstraint = NSLayoutConstraint(item: navigationBar, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: statusBarHeight())
let widthConstraint = NSLayoutConstraint(item: navigationBar, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1.0, constant: 0.0)
let horizontalPositionConstraint = NSLayoutConstraint(item: navigationBar, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1.0, constant: 0.0)
addConstraints([topConstraint,widthConstraint,horizontalPositionConstraint])
navigationItem.leftBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "ic_nav_bar_back"), style: .plain, target: self, action: #selector(closeButtonTapped(_:)))
}
func setHidden(_ hidden: Bool, animated: Bool, animation: @escaping (() -> Void)) {
guard isHidden != hidden else { return }
guard animated else {
isHidden = hidden
animation()
return
}
isHidden = false
alpha = hidden ? 1.0 : 0.0
UIView.animate(withDuration: kDefaultAnimationDuration,
delay: 0.0,
options: [.allowAnimatedContent, .allowUserInteraction],
animations: { [weak self] in
self?.alpha = hidden ? 0.0 : 1.0
animation()
},
completion: { [weak self] _ in
self?.alpha = 1.0
self?.isHidden = hidden
})
}
}

View file

@ -0,0 +1,147 @@
import UIKit
final class PhotosTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private enum Settings {
static let animationDurationWithZooming = 2 * kDefaultAnimationDuration
static let animationDurationWithoutZooming = kDefaultAnimationDuration
static let animationDurationEndingViewFadeInRatio = 0.1
static let animationDurationStartingViewFadeOutRatio = 0.05
static let zoomingAnimationSpringDamping: CGFloat = 0.9
static let animationDurationFadeRatio = 4.0 / 9.0
}
var dismissing = false
var startingView: UIView?
var endingView: UIView?
private var shouldPerformZoomingAnimation: Bool {
return startingView != nil && endingView != nil
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
setupTransitionContainerHierarchyWithTransitionContext(transitionContext)
if shouldPerformZoomingAnimation {
performZoomingAnimationWithTransitionContext(transitionContext)
}
performFadeAnimationWithTransitionContext(transitionContext)
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return shouldPerformZoomingAnimation ? Settings.animationDurationWithZooming : Settings.animationDurationWithoutZooming
}
private func fadeDurationForTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) -> TimeInterval {
let transDuration = transitionDuration(using: transitionContext)
return shouldPerformZoomingAnimation ? transDuration * Settings.animationDurationFadeRatio : transDuration
}
private func setupTransitionContainerHierarchyWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
if let toView = transitionContext.view(forKey: UITransitionContextViewKey.to),
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) {
toView.frame = transitionContext.finalFrame(for: toViewController)
let containerView = transitionContext.containerView
if !toView.isDescendant(of: containerView) {
containerView.addSubview(toView)
}
}
if dismissing {
if let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) {
transitionContext.containerView.bringSubview(toFront: fromView)
}
}
}
private func performZoomingAnimationWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let startingView = startingView, let endingView = endingView else { return }
let startingViewForAnimation = startingView.snapshot
let endingViewForAnimation = endingView.snapshot
let finalEndingViewTransform = endingView.transform
let endingViewInitialTransform = startingViewForAnimation.frame.height / endingViewForAnimation.frame.height
let startingViewFinalTransform = 1.0 / endingViewInitialTransform
let translatedStartingViewCenter = startingView.center(inContainerView: containerView)
let translatedEndingViewFinalCenter = endingView.center(inContainerView: containerView)
startingViewForAnimation.center = translatedStartingViewCenter
endingViewForAnimation.transform = endingViewForAnimation.transform.scaledBy(x: endingViewInitialTransform, y: endingViewInitialTransform)
endingViewForAnimation.center = translatedStartingViewCenter
endingViewForAnimation.alpha = 0.0
containerView.addSubview(startingViewForAnimation)
containerView.addSubview(endingViewForAnimation)
endingView.alpha = 0.0
startingView.alpha = 0.0
let transDuration = transitionDuration(using: transitionContext)
let fadeInDuration = transDuration * Settings.animationDurationEndingViewFadeInRatio
let fadeOutDuration = transDuration * Settings.animationDurationStartingViewFadeOutRatio
UIView.animate(withDuration: fadeInDuration,
delay: 0.0,
options: [.allowAnimatedContent,.beginFromCurrentState],
animations: { endingViewForAnimation.alpha = 1.0 },
completion: { _ in
UIView.animate(withDuration: fadeOutDuration,
delay: 0.0,
options: [.allowAnimatedContent,.beginFromCurrentState],
animations: { startingViewForAnimation.alpha = 0.0 },
completion: { _ in
startingViewForAnimation.removeFromSuperview()
})
})
UIView.animate(withDuration: transDuration,
delay: 0.0,
usingSpringWithDamping:Settings.zoomingAnimationSpringDamping,
initialSpringVelocity:0,
options: [.allowAnimatedContent,.beginFromCurrentState],
animations: { () -> Void in
endingViewForAnimation.transform = finalEndingViewTransform
endingViewForAnimation.center = translatedEndingViewFinalCenter
startingViewForAnimation.transform = startingViewForAnimation.transform.scaledBy(x: startingViewFinalTransform, y: startingViewFinalTransform)
startingViewForAnimation.center = translatedEndingViewFinalCenter
},
completion: { [weak self] _ in
endingViewForAnimation.removeFromSuperview()
endingView.alpha = 1.0
startingView.alpha = 1.0
self?.completeTransitionWithTransitionContext(transitionContext)
})
}
private func performFadeAnimationWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
let fadeView = dismissing ? transitionContext.view(forKey: UITransitionContextViewKey.from) : transitionContext.view(forKey: UITransitionContextViewKey.to)
let beginningAlpha: CGFloat = dismissing ? 1.0 : 0.0
let endingAlpha: CGFloat = dismissing ? 0.0 : 1.0
fadeView?.alpha = beginningAlpha
UIView.animate(withDuration: fadeDurationForTransitionContext(transitionContext), animations: {
fadeView?.alpha = endingAlpha
}) { finished in
if !self.shouldPerformZoomingAnimation {
self.completeTransitionWithTransitionContext(transitionContext)
}
}
}
private func completeTransitionWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
if transitionContext.isInteractive {
if transitionContext.transitionWasCancelled {
transitionContext.cancelInteractiveTransition()
} else {
transitionContext.finishInteractiveTransition()
}
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}

View file

@ -0,0 +1,221 @@
import UIKit
@objc(MWMPhotosViewController)
final class PhotosViewController: MWMViewController {
var referenceViewForPhotoWhenDismissingHandler: ((GalleryItemModel) -> UIView?)?
private let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [UIPageViewControllerOptionInterPageSpacingKey: 16.0])
private(set) var photos: GalleryModel
fileprivate let transitionAnimator = PhotosTransitionAnimator()
fileprivate let interactiveAnimator = PhotosInteractionAnimator()
fileprivate let overlayView = PhotosOverlayView(frame: CGRect.zero)
private var overlayViewHidden = false
private lazy var singleTapGestureRecognizer: UITapGestureRecognizer = {
return UITapGestureRecognizer(target: self, action: #selector(handleSingleTapGestureRecognizer(_:)))
}()
private lazy var panGestureRecognizer: UIPanGestureRecognizer = {
return UIPanGestureRecognizer(target: self, action: #selector(handlePanGestureRecognizer(_:)))
}()
fileprivate var interactiveDismissal = false
private var currentPhotoViewController: PhotoViewController? {
return pageViewController.viewControllers?.first as? PhotoViewController
}
fileprivate var currentPhoto: GalleryItemModel? {
return currentPhotoViewController?.photo
}
init(photos: GalleryModel, initialPhoto: GalleryItemModel? = nil, referenceView: UIView? = nil) {
self.photos = photos
super.init(nibName: nil, bundle: nil)
initialSetupWithInitialPhoto(initialPhoto)
transitionAnimator.startingView = referenceView
transitionAnimator.endingView = currentPhotoViewController?.scalingView.imageView
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func initialSetupWithInitialPhoto(_ initialPhoto: GalleryItemModel? = nil) {
overlayView.photosViewController = self
setupPageViewController(initialPhoto: initialPhoto)
modalPresentationStyle = .custom
transitioningDelegate = self
modalPresentationCapturesStatusBarAppearance = true
overlayView.photosViewController = self
}
private func setupPageViewController(initialPhoto: GalleryItemModel? = nil) {
pageViewController.view.backgroundColor = UIColor.clear
pageViewController.delegate = self
pageViewController.dataSource = self
if let photo = initialPhoto {
let photoViewController = initializePhotoViewController(photo: photo)
pageViewController.setViewControllers([photoViewController],
direction: .forward,
animated: false,
completion: nil)
}
overlayView.photo = initialPhoto
}
fileprivate func initializePhotoViewController(photo: GalleryItemModel) -> PhotoViewController {
let photoViewController = PhotoViewController(photo: photo)
singleTapGestureRecognizer.require(toFail: photoViewController.doubleTapGestureRecognizer)
return photoViewController
}
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
view.tintColor = UIColor.white
view.backgroundColor = UIColor.black
pageViewController.view.backgroundColor = UIColor.clear
pageViewController.view.addGestureRecognizer(singleTapGestureRecognizer)
pageViewController.view.addGestureRecognizer(panGestureRecognizer)
addChildViewController(pageViewController)
view.addSubview(pageViewController.view)
pageViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
pageViewController.didMove(toParentViewController: self)
setupOverlayView()
}
private func setupOverlayView() {
overlayView.photo = currentPhoto
overlayView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
overlayView.frame = view.bounds
view.addSubview(overlayView)
setOverlayHidden(false, animated: false)
}
private func setOverlayHidden(_ hidden: Bool, animated: Bool) {
overlayViewHidden = hidden
overlayView.setHidden(hidden, animated: animated) { [weak self] in
self?.setNeedsStatusBarAppearanceUpdate()
}
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
guard presentedViewController == nil else {
super.dismiss(animated: flag, completion: completion)
return
}
transitionAnimator.startingView = currentPhotoViewController?.scalingView.imageView
if let currentPhoto = currentPhoto {
transitionAnimator.endingView = referenceViewForPhotoWhenDismissingHandler?(currentPhoto)
} else {
transitionAnimator.endingView = nil
}
let overlayWasHiddenBeforeTransition = overlayView.isHidden
setOverlayHidden(true, animated: true)
super.dismiss(animated: flag) { [weak self] in
guard let s = self else { return }
let isStillOnscreen = s.view.window != nil
if isStillOnscreen && !overlayWasHiddenBeforeTransition {
s.setOverlayHidden(false, animated: true)
}
completion?()
}
}
// MARK: - Gesture Recognizers
@objc
private func handlePanGestureRecognizer(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began {
interactiveDismissal = true
dismiss(animated: true, completion: nil)
} else {
interactiveDismissal = false
interactiveAnimator.handlePanWithPanGestureRecognizer(gestureRecognizer, viewToPan: pageViewController.view, anchorPoint: CGPoint(x: view.bounds.midX, y: view.bounds.midY))
}
}
@objc
private func handleSingleTapGestureRecognizer(_ gestureRecognizer: UITapGestureRecognizer) {
setOverlayHidden(!overlayView.isHidden, animated: true)
}
//MARK: - Orientations
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .all
}
// MARK: - Status Bar
override var prefersStatusBarHidden: Bool {
return overlayViewHidden
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .fade
}
}
extension PhotosViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transitionAnimator.dismissing = false
return transitionAnimator
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transitionAnimator.dismissing = true
return transitionAnimator
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
if interactiveDismissal {
interactiveAnimator.animator = transitionAnimator
interactiveAnimator.shouldAnimateUsingAnimator = transitionAnimator.endingView != nil
interactiveAnimator.viewToHideWhenBeginningTransition = overlayView
return interactiveAnimator
}
return nil
}
}
extension PhotosViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let photoViewController = viewController as? PhotoViewController,
let photoIndex = photos.items.index(where: { $0 === photoViewController.photo }),
photoIndex - 1 >= 0 else {
return nil
}
let newPhoto = photos.items[photoIndex - 1]
return initializePhotoViewController(photo: newPhoto)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let photoViewController = viewController as? PhotoViewController,
let photoIndex = photos.items.index(where: { $0 === photoViewController.photo }),
photoIndex + 1 < photos.items.count else {
return nil
}
let newPhoto = photos.items[photoIndex + 1]
return initializePhotoViewController(photo: newPhoto)
}
}
extension PhotosViewController: UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
if let currentPhoto = currentPhoto {
overlayView.photo = currentPhoto
}
}
}
}