From bfecaf4777c85460dec36f81bb0ca07a13f35c42 Mon Sep 17 00:00:00 2001 From: Eugene Mozharovsky Date: Tue, 24 Jan 2017 20:20:24 +0300 Subject: [PATCH] Updated the source files for #17 --- .../TestPicker/TestPicker/AppDelegate.swift | 10 +- .../TestPickerTests/TestPickerTests.swift | 44 +- .../ATHImagePickerAssetsViewController.swift | 395 +++++++++--------- .../ATHImagePickerCaptureViewController.swift | 356 ++++++++-------- .../ATHImagePickerController+Extensions.swift | 56 +-- Source/ATHImagePickerController.swift | 363 ++++++++-------- .../basicPhoto.imageset/Contents.json | 21 + .../basicPhoto.imageset/basicPhoto.jpg | Bin 0 -> 29265 bytes .../ATHImagePickerPreviewViewController.swift | 392 ++++++++--------- ...THImagePickerSelectionViewController.swift | 260 ++++++------ Source/ATHImagePickerTabBarController.swift | 324 +++++++------- Source/ATHNavigationBarHandler.swift | 64 +-- Source/ATHPhotoCell.swift | 66 +-- 13 files changed, 1201 insertions(+), 1150 deletions(-) create mode 100644 Source/ATHImagePickerControllerAssets.xcassets/basicPhoto.imageset/Contents.json create mode 100644 Source/ATHImagePickerControllerAssets.xcassets/basicPhoto.imageset/basicPhoto.jpg diff --git a/Examples/ATHImagePickerController/Storyboard/TestPicker/TestPicker/AppDelegate.swift b/Examples/ATHImagePickerController/Storyboard/TestPicker/TestPicker/AppDelegate.swift index 73afc72..d687b37 100644 --- a/Examples/ATHImagePickerController/Storyboard/TestPicker/TestPicker/AppDelegate.swift +++ b/Examples/ATHImagePickerController/Storyboard/TestPicker/TestPicker/AppDelegate.swift @@ -10,10 +10,10 @@ import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - return true - } + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + return true + } } diff --git a/Examples/ATHImagePickerController/Storyboard/TestPicker/TestPickerTests/TestPickerTests.swift b/Examples/ATHImagePickerController/Storyboard/TestPicker/TestPickerTests/TestPickerTests.swift index 8d061c4..4861f65 100644 --- a/Examples/ATHImagePickerController/Storyboard/TestPicker/TestPickerTests/TestPickerTests.swift +++ b/Examples/ATHImagePickerController/Storyboard/TestPicker/TestPickerTests/TestPickerTests.swift @@ -10,27 +10,27 @@ import XCTest @testable import TestPicker class TestPickerTests: XCTestCase { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - + } + } diff --git a/Source/ATHImagePickerAssetsViewController.swift b/Source/ATHImagePickerAssetsViewController.swift index 1b13575..2f7a9c5 100644 --- a/Source/ATHImagePickerAssetsViewController.swift +++ b/Source/ATHImagePickerAssetsViewController.swift @@ -11,117 +11,133 @@ import Photos import ImagePickerKit public protocol AssetsController: class { - var offset: CGPoint { get set } + var offset: CGPoint { get set } } open class ATHImagePickerAssetsViewController: UIViewController, AssetsController, EmbededController { - - // MARK: - Outlets - - @IBOutlet weak var collectionView: UICollectionView! - - // MARK: - AssetsController properties - - public var offset: CGPoint = .zero { - didSet { - collectionView.contentOffset = offset - } + + // MARK: - Outlets + + @IBOutlet weak var collectionView: UICollectionView! + + // MARK: - AssetsController properties + + public var offset: CGPoint = .zero { + didSet { + collectionView.contentOffset = offset } + } + + // MARK: - EmbededController properties + + internal weak var holder: SelectionController! + + // MARK: - Properties + + /// Photo that is used when user's library is empty. + internal static var basicPhoto: UIImage? + + open var space: CGFloat = 2 + + public lazy var fetchResult: PHFetchResult = { () -> PHFetchResult in + let options = PHFetchOptions() + options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] + let fetchResult = PHAsset.fetchAssets(with: .image, options: options) - // MARK: - EmbededController properties - - internal weak var holder: SelectionController! - - // MARK: - Properties + return fetchResult + }() + + public lazy var cachingImageManager = PHCachingImageManager() + + public var previousPreheatRect: CGRect = .zero + + fileprivate var cellSize: CGSize { + let side = (collectionView.frame.width - space * 3) / 4 + return CGSize( + width: side, + height: side + ) + } + + fileprivate lazy var observer: CollectionViewChangeObserver = { + return CollectionViewChangeObserver(collectionView: self.collectionView, source: self) + }() + + fileprivate var reloaded = false + + // MARK: - Life cycle + + override open func viewDidLoad() { + super.viewDidLoad() - open var space: CGFloat = 2 + resetCachedAssets() + checkPhotoAuth() - public lazy var fetchResult: PHFetchResult = { () -> PHFetchResult in - let options = PHFetchOptions() - let fetchResult = PHAsset.fetchAssets(with: .image, options: options) - - return fetchResult - }() - - public lazy var cachingImageManager = PHCachingImageManager() - - public var previousPreheatRect: CGRect = .zero - - fileprivate var cellSize: CGSize { - let side = (collectionView.frame.width - space * 3) / 4 - return CGSize( - width: side, - height: side - ) + if fetchResult.count > 0 { + collectionView.reloadData() + collectionView.selectItem( + at: IndexPath(row: 0, section: 0), animated: false, scrollPosition: UICollectionViewScrollPosition()) } - fileprivate lazy var observer: CollectionViewChangeObserver = { - return CollectionViewChangeObserver(collectionView: self.collectionView, source: self) - }() - - fileprivate var reloaded = false + PHPhotoLibrary.shared().register(observer) - // MARK: - Life cycle + collectionView.backgroundColor = .clear + } + + open override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + collectionView.layoutIfNeeded() + } + + override open func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) - override open func viewDidLoad() { - super.viewDidLoad() - - resetCachedAssets() - checkPhotoAuth() - - if fetchResult.count > 0 { - collectionView.reloadData() - collectionView.selectItem( - at: IndexPath(row: 0, section: 0), animated: false, scrollPosition: UICollectionViewScrollPosition()) + if !reloaded { + guard let firstAsset = fetchResult.firstObject else { + if ATHImagePickerAssetsViewController.basicPhoto == nil { + let bundle = Bundle(for: self.classForCoder) + ATHImagePickerAssetsViewController.basicPhoto = UIImage(named: "basicPhoto", in: bundle, compatibleWith: nil) } - PHPhotoLibrary.shared().register(observer) + holder.previewController?.image = ATHImagePickerAssetsViewController.basicPhoto - collectionView.backgroundColor = .clear - } - - open override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - collectionView.layoutIfNeeded() - } - - override open func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) + debugPrint("[ATHImagePickerController] Could not get the first asset!") - if !reloaded { - let firstReversed = fetchResult[fetchResult.count - 1] - cachingImageManager.requestImage( - for: firstReversed, - targetSize: UIScreen.main.bounds.size, - contentMode: .aspectFill, - options: nil) { result, info in - if info!["PHImageFileURLKey"] != nil { - self.holder.previewController?.image = result - } - } - } + return + } + + cachingImageManager.requestImage( + for: firstAsset, + targetSize: UIScreen.main.bounds.size, + contentMode: .aspectFill, + options: nil) { result, info in + if info!["PHImageFileURLKey"] != nil { + self.holder.previewController?.image = result + } + } } + } + + override open func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + updateCachedAssets(for: collectionView.bounds, targetSize: cellSize) - override open func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - updateCachedAssets(for: collectionView.bounds, targetSize: cellSize) - - if !reloaded { - reloaded = true - - if collectionView.frame.width != parent!.view.frame.width { - collectionView.reloadData() - collectionView.selectItem( - at: IndexPath(row: 0, section: 0), animated: false, scrollPosition: UICollectionViewScrollPosition()) - } - } + if !reloaded { + reloaded = true + + if collectionView.frame.width != parent!.view.frame.width { + collectionView.reloadData() + collectionView.selectItem( + at: IndexPath(row: 0, section: 0), animated: false, scrollPosition: UICollectionViewScrollPosition()) + } } - - deinit { - if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized { - PHPhotoLibrary.shared().unregisterChangeObserver(observer) - } + } + + deinit { + if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized { + PHPhotoLibrary.shared().unregisterChangeObserver(observer) } + } } @@ -132,119 +148,126 @@ extension ATHImagePickerAssetsViewController: PhotoFetchable { } // MARK: - PhotoCachable extension ATHImagePickerAssetsViewController: PhotoCachable { - public func checkPhotoAuth() { - - PHPhotoLibrary.requestAuthorization { (status) -> Void in - switch status { - case .authorized: - self.cachingImageManager = PHCachingImageManager() - if self.fetchResult.count > 0 { - // TODO: Set main initial image - } - - case .restricted, .denied: - DispatchQueue.main.async(execute: { () -> Void in - self.holder.commit(error: .error("Could not get permission to access the Photo Library!")) - }) - default: - break - } - } - } + public func checkPhotoAuth() { - public func cachingAssets(at rect: CGRect) -> [PHAsset] { - let indexPaths = collectionView.indexPaths(for: rect) - return assets(at: indexPaths, in: fetchResult as! PHFetchResult) + PHPhotoLibrary.requestAuthorization { (status) -> Void in + switch status { + case .authorized: + self.cachingImageManager = PHCachingImageManager() + if self.fetchResult.count > 0 { + // TODO: Set main initial image + } + + case .restricted, .denied: + DispatchQueue.main.async(execute: { () -> Void in + self.holder.commit(error: .error("Could not get permission to access the Photo Library!")) + }) + default: + break + } } + } + + public func cachingAssets(at rect: CGRect) -> [PHAsset] { + let indexPaths = collectionView.indexPaths(for: rect) + return assets(at: indexPaths, in: fetchResult as! PHFetchResult) + } } extension ATHImagePickerAssetsViewController { - public func scrollViewDidScroll(_ scrollView: UIScrollView) { - if scrollView == collectionView { - updateCachedAssets(for: collectionView.bounds, targetSize: cellSize) - - holder.offset = collectionView.contentOffset - - if scrollView.contentOffset.y < 0 { - holder.previewController?.allowPanOutside = true - } else { - holder.previewController?.allowPanOutside = false - } - } + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + if scrollView == collectionView { + updateCachedAssets(for: collectionView.bounds, targetSize: cellSize) + + holder.offset = collectionView.contentOffset + + if scrollView.contentOffset.y < 0 { + holder.previewController?.allowPanOutside = true + } else { + holder.previewController?.allowPanOutside = false + } } + } } extension ATHImagePickerAssetsViewController: UICollectionViewDataSource, UICollectionViewDelegate { - public func numberOfSections(in collectionView: UICollectionView) -> Int { - return 1 - } + public func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return fetchResult.count + } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ATHPhotoCell.identifier, for: indexPath) as! ATHPhotoCell - public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return fetchResult.count + guard indexPath.item < fetchResult.count else { + return cell } - public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ATHPhotoCell.identifier, for: indexPath) as! ATHPhotoCell - - let reversedIndex = fetchResult.count - indexPath.item - 1 - let asset = fetchResult[reversedIndex] - cachingImageManager.requestImage( - for: asset, - targetSize: cellSize, - contentMode: .aspectFill, - options: nil) { [cell] result, info in - - cell.photoImageView.image = result - - } + let asset = fetchResult[indexPath.item] + cachingImageManager.requestImage( + for: asset, + targetSize: cellSize, + contentMode: .aspectFill, + options: nil) { [cell] result, info in - cell.backgroundColor = .red + cell.photoImageView.image = result - return cell } - public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - let reversedIndex = fetchResult.count - indexPath.item - 1 - - let asset = fetchResult[reversedIndex] - cachingImageManager.requestImage( - for: asset, - targetSize: UIScreen.main.bounds.size, - contentMode: .aspectFill, - options: nil) { result, info in - if info!["PHImageFileURLKey"] != nil { - if let previewController = self.holder.previewController, previewController.state == .folded { - let floatingView = self.holder.floatingView - previewController.restore(view: floatingView, to: .unfolded, animated: true) - previewController.animationCompletion = { _ in - self.collectionView.scrollToItem(at: indexPath, at: .top, animated: true) - previewController.animationCompletion = nil - } - } - - DispatchQueue.main.async { - self.holder.previewController?.image = result - } - } - } - - } -} - -extension ATHImagePickerAssetsViewController: UICollectionViewDelegateFlowLayout { - public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { - return space - } + cell.backgroundColor = .red - public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { - return space - } + return cell + } + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let reversedIndex = fetchResult.count - indexPath.item - 1 - public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { - return UIEdgeInsets.zero + guard indexPath.item < fetchResult.count else { + return } - public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - return cellSize + let asset = fetchResult[indexPath.item] + cachingImageManager.requestImage( + for: asset, + targetSize: UIScreen.main.bounds.size, + contentMode: .aspectFill, + options: nil) { result, info in + if info!["PHImageFileURLKey"] != nil { + if let previewController = self.holder.previewController, previewController.state == .folded { + let floatingView = self.holder.floatingView + previewController.restore(view: floatingView, to: .unfolded, animated: true) + previewController.animationCompletion = { _ in + self.collectionView.scrollToItem(at: indexPath, at: .top, animated: true) + previewController.animationCompletion = nil + } + } + + DispatchQueue.main.async { + self.holder.previewController?.image = result + } + } } + + } +} + +extension ATHImagePickerAssetsViewController: UICollectionViewDelegateFlowLayout { + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return space + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return space + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return UIEdgeInsets.zero + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return cellSize + } } diff --git a/Source/ATHImagePickerCaptureViewController.swift b/Source/ATHImagePickerCaptureViewController.swift index 7550b4a..e78fc9e 100644 --- a/Source/ATHImagePickerCaptureViewController.swift +++ b/Source/ATHImagePickerCaptureViewController.swift @@ -12,187 +12,187 @@ import AVFoundation import ImagePickerKit final public class ATHImagePickerCaptureViewController: UIViewController, PhotoCapturable, StatusBarUpdatable { - - typealias Config = ATHImagePickerStatusBarConfig - - // MARK: - Outlets - - @IBOutlet weak var cameraView: UIView! - @IBOutlet weak var flashButton: UIButton! - @IBOutlet weak var switchButton: UIButton! - - // MARK: - Capturable properties - - public var session: AVCaptureSession? - - public var device: AVCaptureDevice? { - didSet { - guard let device = device else { return } - if !device.hasFlash { - flashButton.isHidden = true - } - } - } - - public var videoInput: AVCaptureDeviceInput? - public var imageOutput: AVCaptureStillImageOutput? - - public var focusView: UIView? - - lazy public var previewViewContainer: UIView = { - return self.cameraView - }() - - public var captureNotificationObserver: CaptureNotificationObserver? - - // MARK: Properties - - public static let identifier = "ATHImagePickerCaptureViewController" - - open override var prefersStatusBarHidden: Bool { - return isStatusBarHidden - } - - open override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { - return statusBarAnimation - } - - fileprivate var flashMode: AVCaptureFlashMode = .on { - didSet { - let bundle = Bundle(for: self.classForCoder) - - switch flashMode { - case .on: - let image = /*config.assets?.flashOnImage ??*/ - UIImage(named: "Flash", in: bundle, compatibleWith: nil) - - flashButton.setImage(image, for: .normal) - - case .off: - let image = /*config.assets?.flashOffImage ??*/ - UIImage(named: "FlashOff", in: bundle, compatibleWith: nil) - - flashButton.setImage(image, for: .normal) - - case .auto: - let image = /*config.assets?.flashAutoImage ??*/ - UIImage(named: "FlashAuto", in: bundle, compatibleWith: nil) - - flashButton.setImage(image, for: .normal) - } - - setFlashMode(flashMode) - } - } - - fileprivate var queue = OperationQueue() - - internal weak var commiterDelegate: ATHImagePickerCommiterDelegate? { - didSet { - setupConfig() - } - } - - fileprivate let handler = ATHNavigationBarHandler() - - fileprivate var config: ATHImagePickerPageConfig! { - didSet { - handler.setupItem(navigationItem, config: config, ignoreRight: true, leftHandler: { [weak self] in - self?.commiterDelegate?.commit(item: ATHImagePickerItem(image: nil)) - }, rightHandler: nil) - } - } - - fileprivate var isStatusBarHidden: Bool = false { - didSet { - updateStatusBar(with: config.statusBarConfig) - } - } - - fileprivate var statusBarAnimation: UIStatusBarAnimation = .none { - didSet { - updateStatusBar(with: config.statusBarConfig) - } - } - - // MARK: - Life cycle - - override public func viewDidLoad() { - super.viewDidLoad() - - queue.addOperation { - self.prepareForCapturing() - self.setFlashMode(self.flashMode) - } + + typealias Config = ATHImagePickerStatusBarConfig + + // MARK: - Outlets + + @IBOutlet weak var cameraView: UIView! + @IBOutlet weak var flashButton: UIButton! + @IBOutlet weak var switchButton: UIButton! + + // MARK: - Capturable properties + + public var session: AVCaptureSession? + + public var device: AVCaptureDevice? { + didSet { + guard let device = device else { return } + if !device.hasFlash { + flashButton.isHidden = true + } + } + } + + public var videoInput: AVCaptureDeviceInput? + public var imageOutput: AVCaptureStillImageOutput? + + public var focusView: UIView? + + lazy public var previewViewContainer: UIView = { + return self.cameraView + }() + + public var captureNotificationObserver: CaptureNotificationObserver? + + // MARK: Properties + + public static let identifier = "ATHImagePickerCaptureViewController" + + open override var prefersStatusBarHidden: Bool { + return isStatusBarHidden + } + + open override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { + return statusBarAnimation + } + + fileprivate var flashMode: AVCaptureFlashMode = .on { + didSet { + let bundle = Bundle(for: self.classForCoder) + + switch flashMode { + case .on: + let image = /*config.assets?.flashOnImage ??*/ + UIImage(named: "Flash", in: bundle, compatibleWith: nil) - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(ATHImagePickerCaptureViewController.recognizedTapGesture(_:))) - previewViewContainer.addGestureRecognizer(tapRecognizer) - } - - override public func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - reloadPreview(previewViewContainer) - } - - public override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - reloadPreview(previewViewContainer) - pageTabBarItem.titleColor = config.titleColor - } - - public override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - pageTabBarItem.titleColor = config.titleInactiveColor - } - - // MARK: - Setup utils - - fileprivate func setupConfig() { - guard let config = commiterDelegate?.commit(configFor: .camera) else { - return - } + flashButton.setImage(image, for: .normal) - self.config = config + case .off: + let image = /*config.assets?.flashOffImage ??*/ + UIImage(named: "FlashOff", in: bundle, compatibleWith: nil) - pageTabBarItem.title = config.title - pageTabBarItem.titleColor = view.window != nil ? config.titleColor : config.titleInactiveColor - pageTabBarItem.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: UIFontWeightMedium) + flashButton.setImage(image, for: .normal) - isStatusBarHidden = config.statusBarConfig.isStatusBarHidden - statusBarAnimation = config.statusBarConfig.statusBarAnimation + case .auto: + let image = /*config.assets?.flashAutoImage ??*/ + UIImage(named: "FlashAuto", in: bundle, compatibleWith: nil) - let bundle = Bundle(for: self.classForCoder) - let image = /*config.assets?.switchCameraIcon ??*/ - UIImage(named: "FlipCamera", in: bundle, compatibleWith: nil) - switchButton.setImage(image, for: .normal) - } - - - // MARK: - IBActions - - @IBAction func recognizedTapGesture(_ rec: UITapGestureRecognizer) { - let point = rec.location(in: previewViewContainer) - focus(at: point) - } - - @IBAction func didPressCapturePhoto(_ sender: AnyObject) { - captureStillImage { image in - self.commiterDelegate?.commit(item: ATHImagePickerItem(image: image)) - } - } - - @IBAction func didPressFlipButton(_ sender: AnyObject) { - flipCamera() - } - - @IBAction func didPressFlashButton(_ sender: AnyObject) { - switch flashMode { - case .auto: - flashMode = .on - case .on: - flashMode = .off - case .off: - flashMode = .auto - } - } + flashButton.setImage(image, for: .normal) + } + + setFlashMode(flashMode) + } + } + + fileprivate var queue = OperationQueue() + + internal weak var commiterDelegate: ATHImagePickerCommiterDelegate? { + didSet { + setupConfig() + } + } + + fileprivate let handler = ATHNavigationBarHandler() + + fileprivate var config: ATHImagePickerPageConfig! { + didSet { + handler.setupItem(navigationItem, config: config, ignoreRight: true, leftHandler: { [weak self] in + self?.commiterDelegate?.commit(item: ATHImagePickerItem(image: nil)) + }, rightHandler: nil) + } + } + + fileprivate var isStatusBarHidden: Bool = false { + didSet { + updateStatusBar(with: config.statusBarConfig) + } + } + + fileprivate var statusBarAnimation: UIStatusBarAnimation = .none { + didSet { + updateStatusBar(with: config.statusBarConfig) + } + } + + // MARK: - Life cycle + + override public func viewDidLoad() { + super.viewDidLoad() + + queue.addOperation { + self.prepareForCapturing() + self.setFlashMode(self.flashMode) + } + + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(ATHImagePickerCaptureViewController.recognizedTapGesture(_:))) + previewViewContainer.addGestureRecognizer(tapRecognizer) + } + + override public func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + reloadPreview(previewViewContainer) + } + + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + reloadPreview(previewViewContainer) + pageTabBarItem.titleColor = config.titleColor + } + + public override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + pageTabBarItem.titleColor = config.titleInactiveColor + } + + // MARK: - Setup utils + + fileprivate func setupConfig() { + guard let config = commiterDelegate?.commit(configFor: .camera) else { + return + } + + self.config = config + + pageTabBarItem.title = config.title + pageTabBarItem.titleColor = view.window != nil ? config.titleColor : config.titleInactiveColor + pageTabBarItem.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: UIFontWeightMedium) + + isStatusBarHidden = config.statusBarConfig.isStatusBarHidden + statusBarAnimation = config.statusBarConfig.statusBarAnimation + + let bundle = Bundle(for: self.classForCoder) + let image = /*config.assets?.switchCameraIcon ??*/ + UIImage(named: "FlipCamera", in: bundle, compatibleWith: nil) + switchButton.setImage(image, for: .normal) + } + + + // MARK: - IBActions + + @IBAction func recognizedTapGesture(_ rec: UITapGestureRecognizer) { + let point = rec.location(in: previewViewContainer) + focus(at: point) + } + + @IBAction func didPressCapturePhoto(_ sender: AnyObject) { + captureStillImage { image in + self.commiterDelegate?.commit(item: ATHImagePickerItem(image: image)) + } + } + + @IBAction func didPressFlipButton(_ sender: AnyObject) { + flipCamera() + } + + @IBAction func didPressFlashButton(_ sender: AnyObject) { + switch flashMode { + case .auto: + flashMode = .on + case .on: + flashMode = .off + case .off: + flashMode = .auto + } + } } diff --git a/Source/ATHImagePickerController+Extensions.swift b/Source/ATHImagePickerController+Extensions.swift index 421013a..a243a11 100644 --- a/Source/ATHImagePickerController+Extensions.swift +++ b/Source/ATHImagePickerController+Extensions.swift @@ -6,41 +6,41 @@ // Copyright © 2017 Athlee LLC. All rights reserved. // -import UIKit +import UIKit internal extension UIColor { - convenience init(hex: Int) { - let red = CGFloat((hex & 0xFF0000) >> 16) / CGFloat(255) - let green = CGFloat((hex & 0xFF00) >> 8) / CGFloat(255) - let blue = CGFloat((hex & 0xFF) >> 0) / CGFloat(255) - - self.init(red: red, green: green, blue: blue, alpha: 1) - } + convenience init(hex: Int) { + let red = CGFloat((hex & 0xFF0000) >> 16) / CGFloat(255) + let green = CGFloat((hex & 0xFF00) >> 8) / CGFloat(255) + let blue = CGFloat((hex & 0xFF) >> 0) / CGFloat(255) + + self.init(red: red, green: green, blue: blue, alpha: 1) + } } internal extension UIView { - func snapshot() -> UIImage { - UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0) - drawHierarchy(in: bounds, afterScreenUpdates: false) - let image = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - - return image! - } + func snapshot() -> UIImage { + UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0) + drawHierarchy(in: bounds, afterScreenUpdates: false) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image! + } } internal extension UICollectionView { - func indexPaths(for rect: CGRect) -> [IndexPath] { - guard let allLayoutAttributes = collectionViewLayout.layoutAttributesForElements(in: rect) else { - return [] - } - - guard allLayoutAttributes.count > 0 else { - return [] - } - - let indexPaths = allLayoutAttributes.map { $0.indexPath } - - return indexPaths + func indexPaths(for rect: CGRect) -> [IndexPath] { + guard let allLayoutAttributes = collectionViewLayout.layoutAttributesForElements(in: rect) else { + return [] + } + + guard allLayoutAttributes.count > 0 else { + return [] } + + let indexPaths = allLayoutAttributes.map { $0.indexPath } + + return indexPaths + } } diff --git a/Source/ATHImagePickerController.swift b/Source/ATHImagePickerController.swift index 9426df6..90aafc2 100644 --- a/Source/ATHImagePickerController.swift +++ b/Source/ATHImagePickerController.swift @@ -9,26 +9,26 @@ import UIKit // -// MARK: - Internal helpers' protocols +// MARK: - Internal helpers' protocols // internal typealias Color = ATHImagePickerColor internal protocol StatusBarUpdatable { - associatedtype Config - func updateStatusBar(with config: Config) + associatedtype Config + func updateStatusBar(with config: Config) } extension StatusBarUpdatable where Self: UIViewController, Self.Config == ATHImagePickerStatusBarConfig { - func updateStatusBar(with config: Config) { - if config.isAnimated { - UIView.animate(withDuration: config.animationDuration) { - self.setNeedsStatusBarAppearanceUpdate() - } - } else { - setNeedsStatusBarAppearanceUpdate() - } + func updateStatusBar(with config: Config) { + if config.isAnimated { + UIView.animate(withDuration: config.animationDuration) { + self.setNeedsStatusBarAppearanceUpdate() + } + } else { + setNeedsStatusBarAppearanceUpdate() } + } } // @@ -36,209 +36,214 @@ extension StatusBarUpdatable where Self: UIViewController, Self.Config == ATHIma // public struct ATHImagePickerColor { - public static let black = UIColor.black - public static let tin = UIColor(hex: 0x7F7F7F) - public static let blue = UIColor(hex: 0x007AFF) + public static let black = UIColor.black + public static let tin = UIColor(hex: 0x7F7F7F) + public static let blue = UIColor(hex: 0x007AFF) } public protocol ATHImagePickerCommiterDelegate: class { - func commit(item: ATHImagePickerItem) - func commit(error: ATHImagePickerError?) - func commit(configFor sourceType: ATHImagePickerSourceType) -> ATHImagePickerPageConfig + func commit(item: ATHImagePickerItem) + func commit(error: ATHImagePickerError?) + func commit(configFor sourceType: ATHImagePickerSourceType) -> ATHImagePickerPageConfig } public struct ATHImagePickerSourceType: OptionSet { - public static let library = ATHImagePickerSourceType(rawValue: 1 << 0) - public static let camera = ATHImagePickerSourceType(rawValue: 1 << 1) - - public let rawValue: Int - - public init(rawValue: Int) { - self.rawValue = rawValue - } + public static let library = ATHImagePickerSourceType(rawValue: 1 << 0) + public static let camera = ATHImagePickerSourceType(rawValue: 1 << 1) + + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } } public struct ATHImagePickerItem { - public let image: UIImage? + public let image: UIImage? } public enum ATHImagePickerError { - case error(String) + case error(String) } public protocol ATHImagePickerControllerDelegate: class { - func imagePickerController(_ picker: ATHImagePickerController, didCancelWithItem item: ATHImagePickerItem) - func imagePickerController(_ picker: ATHImagePickerController, didCancelWithError error: ATHImagePickerError?) - func imagePickerController(_ picker: ATHImagePickerController, configFor sourceType: ATHImagePickerSourceType) -> ATHImagePickerPageConfig + func imagePickerController(_ picker: ATHImagePickerController, didCancelWithItem item: ATHImagePickerItem) + func imagePickerController(_ picker: ATHImagePickerController, didCancelWithError error: ATHImagePickerError?) + func imagePickerController(_ picker: ATHImagePickerController, configFor sourceType: ATHImagePickerSourceType) -> ATHImagePickerPageConfig } public struct ATHImagePickerAssets { - public let switchCameraIcon: UIImage? - public let flashOnImage: UIImage? - public let flashOffImage: UIImage? - public let flashAutoImage: UIImage? - - public init(switchCameraIcon: UIImage? = nil, - flashOnImage: UIImage? = nil, - flashOffImage: UIImage? = nil, - flashAutoImage: UIImage? = nil) { - self.switchCameraIcon = switchCameraIcon - self.flashOnImage = flashOnImage - self.flashOffImage = flashOffImage - self.flashAutoImage = flashAutoImage - } + public let switchCameraIcon: UIImage? + public let flashOnImage: UIImage? + public let flashOffImage: UIImage? + public let flashAutoImage: UIImage? + + public init(switchCameraIcon: UIImage? = nil, + flashOnImage: UIImage? = nil, + flashOffImage: UIImage? = nil, + flashAutoImage: UIImage? = nil) { + self.switchCameraIcon = switchCameraIcon + self.flashOnImage = flashOnImage + self.flashOffImage = flashOffImage + self.flashAutoImage = flashAutoImage + } } public struct ATHImagePickerPageConfig { - public let leftButtonTitle: String - public let rightButtonTitle: String - public let leftButtonImage: UIImage? - public let rightButtonImage: UIImage? - public let title: String - public let titleColor: UIColor - public let titleInactiveColor: UIColor - public let leftButtonColor: UIColor - public let rightButtonColor: UIColor - - public let statusBarConfig: ATHImagePickerStatusBarConfig - public let assets: ATHImagePickerAssets? - - public init(leftButtonTitle: String, - rightButtonTitle: String, - leftButtonImage: UIImage?, - rightButtonImage: UIImage?, - title: String, - titleColor: UIColor, - titleInactiveColor: UIColor, - leftButtonColor: UIColor, - rightButtonColor: UIColor, - statusBarConfig: ATHImagePickerStatusBarConfig = ATHImagePickerStatusBarConfig(), - assets: ATHImagePickerAssets? = nil) { - self.leftButtonTitle = leftButtonTitle - self.rightButtonTitle = rightButtonTitle - self.leftButtonImage = leftButtonImage - self.rightButtonImage = rightButtonImage - self.title = title - self.titleColor = titleColor - self.titleInactiveColor = titleInactiveColor - self.leftButtonColor = leftButtonColor - self.rightButtonColor = rightButtonColor - - self.statusBarConfig = statusBarConfig - self.assets = assets - } + public let leftButtonTitle: String + public let rightButtonTitle: String + public let leftButtonImage: UIImage? + public let rightButtonImage: UIImage? + public let title: String + public let titleColor: UIColor + public let titleInactiveColor: UIColor + public let leftButtonColor: UIColor + public let rightButtonColor: UIColor + + public let statusBarConfig: ATHImagePickerStatusBarConfig + public let assets: ATHImagePickerAssets? + + public init(leftButtonTitle: String, + rightButtonTitle: String, + leftButtonImage: UIImage?, + rightButtonImage: UIImage?, + title: String, + titleColor: UIColor, + titleInactiveColor: UIColor, + leftButtonColor: UIColor, + rightButtonColor: UIColor, + statusBarConfig: ATHImagePickerStatusBarConfig = ATHImagePickerStatusBarConfig(), + assets: ATHImagePickerAssets? = nil) { + self.leftButtonTitle = leftButtonTitle + self.rightButtonTitle = rightButtonTitle + self.leftButtonImage = leftButtonImage + self.rightButtonImage = rightButtonImage + self.title = title + self.titleColor = titleColor + self.titleInactiveColor = titleInactiveColor + self.leftButtonColor = leftButtonColor + self.rightButtonColor = rightButtonColor + + self.statusBarConfig = statusBarConfig + self.assets = assets + } } public struct ATHImagePickerStatusBarConfig { - public var isStatusBarHidden = false - public var statusBarAnimation: UIStatusBarAnimation = .none - public var isAnimated = false - public var animationDuration: TimeInterval = 0.3 - - public init(isStatusBarHidden: Bool = false, - statusBarAnimation: UIStatusBarAnimation = .none, - isAnimated: Bool = false, - animationDuration: TimeInterval = 0.3) { - self.isStatusBarHidden = isStatusBarHidden - self.statusBarAnimation = statusBarAnimation - self.isAnimated = isAnimated - self.animationDuration = animationDuration - } + public var isStatusBarHidden = false + public var statusBarAnimation: UIStatusBarAnimation = .none + public var isAnimated = false + public var animationDuration: TimeInterval = 0.3 + + public init(isStatusBarHidden: Bool = false, + statusBarAnimation: UIStatusBarAnimation = .none, + isAnimated: Bool = false, + animationDuration: TimeInterval = 0.3) { + self.isStatusBarHidden = isStatusBarHidden + self.statusBarAnimation = statusBarAnimation + self.isAnimated = isAnimated + self.animationDuration = animationDuration + } } open class ATHImagePickerController: UINavigationController, ATHImagePickerCommiterDelegate { - // MARK: - Static properties - open static var selectedImage: UIImage? - - // MARK: - Properties - open override var prefersStatusBarHidden: Bool { - return true + // MARK: - Static properties + open static var selectedImage: UIImage? + open static var emptyImage: UIImage? { + didSet { + ATHImagePickerAssetsViewController.basicPhoto = emptyImage } - - open var sourceType: ATHImagePickerSourceType = [.camera] - open weak var pickerDelegate: ATHImagePickerControllerDelegate? - - // MARK: - Life cycle - - override open func viewDidLoad() { - super.viewDidLoad() - - let bundle = Bundle(for: self.classForCoder) - let storyboard = UIStoryboard(name: "ATHImagePickerController", bundle: bundle) - - var controllers: [UIViewController] = [] - - if sourceType.contains(.library) { - let selectionViewController = storyboard.instantiateViewController(withIdentifier: ATHImagePickerSelectionViewController.identifier) as! ATHImagePickerSelectionViewController - selectionViewController.commiterDelegate = self - - controllers += [selectionViewController] - } - - if sourceType.contains(.camera) { - let captureViewController = storyboard.instantiateViewController(withIdentifier: ATHImagePickerCaptureViewController.identifier) as! ATHImagePickerCaptureViewController - captureViewController.commiterDelegate = self - - controllers += [captureViewController] - } - - if sourceType.contains([.camera, .library]) { - let controller = ATHImagePickerTabBarController(viewControllers: controllers) - controller.commiterDelegate = self - - setViewControllers([controller], animated: false) - } else { - setViewControllers(controllers, animated: false) - } + } + + // MARK: - Properties + open override var prefersStatusBarHidden: Bool { + return true + } + + open var sourceType: ATHImagePickerSourceType = [.camera] + open weak var pickerDelegate: ATHImagePickerControllerDelegate? + + // MARK: - Life cycle + + override open func viewDidLoad() { + super.viewDidLoad() + + let bundle = Bundle(for: self.classForCoder) + let storyboard = UIStoryboard(name: "ATHImagePickerController", bundle: bundle) + + var controllers: [UIViewController] = [] + + if sourceType.contains(.library) { + let selectionViewController = storyboard.instantiateViewController(withIdentifier: ATHImagePickerSelectionViewController.identifier) as! ATHImagePickerSelectionViewController + selectionViewController.commiterDelegate = self + + controllers += [selectionViewController] } - // MARK: - Static members - - open static func createdChild(sourceType: ATHImagePickerSourceType, delegate: ATHImagePickerCommiterDelegate) -> UIViewController? { - let bundle = Bundle(for: ATHImagePickerController.self.classForCoder()) - let storyboard = UIStoryboard(name: "ATHImagePickerController", bundle: bundle) - - var controllers: [UIViewController] = [] - - if sourceType.contains(.library) { - let selectionViewController = storyboard.instantiateViewController(withIdentifier: ATHImagePickerSelectionViewController.identifier) as! ATHImagePickerSelectionViewController - selectionViewController.commiterDelegate = delegate - - controllers += [selectionViewController] - } - - if sourceType.contains(.camera) { - let captureViewController = storyboard.instantiateViewController(withIdentifier: ATHImagePickerCaptureViewController.identifier) as! ATHImagePickerCaptureViewController - captureViewController.commiterDelegate = delegate - - controllers += [captureViewController] - } - - if sourceType.contains([.camera, .library]) { - let controller = ATHImagePickerTabBarController(viewControllers: controllers) - controller.commiterDelegate = delegate - - return controller - } else { - return controllers.first - } + if sourceType.contains(.camera) { + let captureViewController = storyboard.instantiateViewController(withIdentifier: ATHImagePickerCaptureViewController.identifier) as! ATHImagePickerCaptureViewController + captureViewController.commiterDelegate = self + + controllers += [captureViewController] } - // MARK: - ATHImagePickerCommiterDelegate - - public func commit(item: ATHImagePickerItem) { - pickerDelegate?.imagePickerController(self, didCancelWithItem: item) + if sourceType.contains([.camera, .library]) { + let controller = ATHImagePickerTabBarController(viewControllers: controllers) + controller.commiterDelegate = self + + setViewControllers([controller], animated: false) + } else { + setViewControllers(controllers, animated: false) + } + } + + // MARK: - Static members + + open static func createdChild(sourceType: ATHImagePickerSourceType, delegate: ATHImagePickerCommiterDelegate) -> UIViewController? { + let bundle = Bundle(for: ATHImagePickerController.self.classForCoder()) + let storyboard = UIStoryboard(name: "ATHImagePickerController", bundle: bundle) + + var controllers: [UIViewController] = [] + + if sourceType.contains(.library) { + let selectionViewController = storyboard.instantiateViewController(withIdentifier: ATHImagePickerSelectionViewController.identifier) as! ATHImagePickerSelectionViewController + selectionViewController.commiterDelegate = delegate + + controllers += [selectionViewController] } - public func commit(error: ATHImagePickerError?) { - pickerDelegate?.imagePickerController(self, didCancelWithError: error) + if sourceType.contains(.camera) { + let captureViewController = storyboard.instantiateViewController(withIdentifier: ATHImagePickerCaptureViewController.identifier) as! ATHImagePickerCaptureViewController + captureViewController.commiterDelegate = delegate + + controllers += [captureViewController] } - public func commit(configFor sourceType: ATHImagePickerSourceType) -> ATHImagePickerPageConfig { - guard let delegate = pickerDelegate else { - fatalError("Could not load a config from delegate!") - } - - return delegate.imagePickerController(self, configFor: sourceType) + if sourceType.contains([.camera, .library]) { + let controller = ATHImagePickerTabBarController(viewControllers: controllers) + controller.commiterDelegate = delegate + + return controller + } else { + return controllers.first } + } + + // MARK: - ATHImagePickerCommiterDelegate + + public func commit(item: ATHImagePickerItem) { + pickerDelegate?.imagePickerController(self, didCancelWithItem: item) + } + + public func commit(error: ATHImagePickerError?) { + pickerDelegate?.imagePickerController(self, didCancelWithError: error) + } + + public func commit(configFor sourceType: ATHImagePickerSourceType) -> ATHImagePickerPageConfig { + guard let delegate = pickerDelegate else { + fatalError("Could not load a config from delegate!") + } + + return delegate.imagePickerController(self, configFor: sourceType) + } } diff --git a/Source/ATHImagePickerControllerAssets.xcassets/basicPhoto.imageset/Contents.json b/Source/ATHImagePickerControllerAssets.xcassets/basicPhoto.imageset/Contents.json new file mode 100644 index 0000000..8a44c00 --- /dev/null +++ b/Source/ATHImagePickerControllerAssets.xcassets/basicPhoto.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "basicPhoto.jpg", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Source/ATHImagePickerControllerAssets.xcassets/basicPhoto.imageset/basicPhoto.jpg b/Source/ATHImagePickerControllerAssets.xcassets/basicPhoto.imageset/basicPhoto.jpg new file mode 100644 index 0000000000000000000000000000000000000000..07bc99a05d630e2f268c31575f9c7cdbb6655ff9 GIT binary patch literal 29265 zcmdSB2Ut|e(l9*aB)Vil0Tszfas~rIh9qGKLy|Z!@GpDAyPj_{7pRVevI(tf;IA!odN7 zaDWf!d?lV0wCvi@WmbH(_{zA4<494($Y{=R#o`5X|jcj99-b*;OOqE zrF8#>z5(;IK7P%rNVg7ZLT9JK{0^1grc zt>u{n`9Dhi$@M2oD>n;(1@b_?k2%md0Dl5tYI7Z}2LRj$0uf)bbg;Apfk-9+{7cxc zWk_u<%D{eC#@@rh76c;ozrZ_KnYjV*YXH7&Zs+U{z@-;>*Wd2_@+k_fg9%bW)^lg+$_xOFKYeK>0+#YtMm3RAkYfG<0datML4qJLkQC@HNC5-}X@GP<1|SoV1;_^E0CEL+f&4)~gI<8bL9aov zpd?T_C>xXyDgu>&%0N}1&!8qyE2s;E1&x3vL9?J`&<1D^bc}<8Lxe+)Lxn?+!;Hg< z!;K?|BaS1BqkyA|qlIID^B4z)!;8bq#4Ez9z-z|q#hb)i!8^n! z#HYf)fzOLCh5ryAf^UKEf**+g3O@<|9sURW2K-+9Df|umGXipg>jc~ck_5^G`UEfn zFM<~YaRj*p7=lIuEWs?n9w8wiEg?Ih7~w-g141~VAK@#)R6-QtC&F&R8Nyv6A|iSs zZXy{X4I(olccK?Wi9`iNwM0EcvqT5PWW>zGg2al%P+~{or^Io@2;v&z9^!f8W0Idp zSV<&FG)OE-d`O~5vPmjPI!R_pj!7v<*-2$cbx7f)Ka(bq7LhiRj*@PZk&)dZlOWS1 zvn6{%mO%EN>@bJL$`nR+SR*Pt*(Y$MP0>SJ)*xsuRw1{|AxMdevAR1fr~+t!HXe{ zp^;(b8uc})YZlkSu9aLHzK(mH>$=u;zw24o+ph02GBGMKIx{9R)-$d!(K6j-f-}W1 z)i5nGQ!&dj+cL*6*D^2Npt*7HhW(9%8x1!$ZeG9n@TSMjjGG-dk8iQxg4}v~3w3LZ zg_K2{#hN9CrH*CeHuG(@+y1waw})9tSjAawSmRlnS@+r4+4R^#*vi-z*sroHv-`3m z*+)6ZIpjE8I5Iiqxx0A?cqDinc`|th zcrWqZ^ma@lmcLZrveoMTY_AI z7J|uweM00yib6p`AB8rBxrMES(}V{_s6^C6LPY9BPDRB;T}6vT7sYOiJr+w58xW@! zerUBAW)`->^d&K_8vH4-s;VbpKTZtQLR)r7&s-lW>}im90?`Z2*{y~l{hCuSOEnP$7@%H}EN8x{{N z;w_df?^?!KE?UW0y|tRRma%?oy#SMeMZ*?tR%{>GCfRPnAHvh&`*s?3d3NXa zdiE#>Vu!~L<&Kn&c8(2BOitcT-Ok+3FPtY`Bwb=%He6L)bKP*nB=-uYS)Bk7xsepR{8G$%~=7DuV zEJ4qMW}hfMdG|Bv&vrkzKfUwx?bDrSde17JGd_Rvd?r{a82RGz3)dF|AyOgfFY#a6 zzU&AU3QY_>3$qMs3BMB_7k=`};#Et8Ktw{sd8Bn@N0ew(+H1nsj<5UQ$h~>@mg24N z+nH#!=npYBV!~o}VohVe#tFxz$CJi;#7`!uCVWi1o%lNOB*`WTn|wbRm2y2LG-W^4 zGPNh|Zdy?~V|sY{(J!{Y3}z^0e8^n|!DB zt|4D6KOb=&5rxD>dLS1Ij0(C6S+l-KSq=b1Xwx{-SQ`o0FuhK@#+#xG5ZP4&(9nrpwve5v{>`L(=7yrryFwDm)qNE@bI zq#e^C+VP=Ntn*`+L{~+(bazdUT+ioT`QD~J<-S&|I<~tX(myz0G%z`6Ik+%nKeRdQ zIea|wWRzeuZ0x78xbf@bnG>87s7cYu>M8lDwrTC@kr|7b{87|E(J1-xvyjZ1JOhy_1LQ4Q*#L3#%KvH!hTz-xP=+_WMsg#XJ!NDhmdIt`7eb2Q>5Ox2As6 z+kSjH*6g*w6}k<>Hli^2mJXbo&VIC-L1RY%GJ^Af+92fUq{FFf<`wdS9e(ckW1lKyKDTg1?f4b4eah^Cr&E zw}EW}Q49!l8g+htoN<1B3h)AN1O)ozaXtci4#LC5#U=PPU0et+AOJ%|L`aN#0sNW> z0qEin0Vx3i5itn~2`&NIB~s!`gv2DoIC%K@gam}d1cXGyfSN=6AE)zAAPQojW>P$y zTOeEt96Sn~^9Im$VDkWIaToibe#_5E(xHU3LFI17A({KY3VG zT*#eaX7sH?jf`}j&DBO<&p@J%q)VJJr?8Vqk1tD zk0lt6=OVma4%POb#Z34Rv6u$r)*KrJY6pA^Pa8X(mXI9p*lBcI+E%F%A0MV>kF zc`#6E4D3tO5p$hX$-c;(SJ#|HO4mdqnbCZFrM_jS%AtGS>)!$!Yxoafrg}Kx=b#{+ z?$xDomqyg(rE}2dpv@De^`YEqxA9IBLew%!@mMb}ps4ve)VcLktG1_t>>RY3Xgt0D z!bJf(Xn@_`E&j-GeAc+=XkRw)LJ_qRNDT(heba-8X!p$pWe0FPxAl%ek_qYjuX7rQ zJ`*m?E^K#`>P`h!5}O&77FsJ@4crcHH*ASEIR`!B7jYtO_p6sg!XTgQMDoS1wl}%C z%DY;B6G~2=Wd`4g6Bw}jwrW`IIiC?yzsPV7TI6fZIw+W`VbMht3>9nSMK#!*X^&A! zseBb!YisK0D26nswV#8uv86#2r5a~igCM11kbmfD1cgRDxh;xQ8%RkH!B>CBit2lWb%y8AOz`;%uCI?=nLCKw((^gRvW zAO7sj|MWx6{F83x7IWdiNYuCdLANMV&*QqUuH`;r4leKeo%^(rj^V;*>daTZmiy7% zQq-eJkrM-(Z-_czJy%yERVN#50zycjtWkVOEK!^)P~jdnl^MZ`km?@vt(Py zI!IV9(~51ln>E*ZsGni7?M-Nc$4CPsp+8ftf|jw)cl>NxS_R&O6oRAzWzsVgv*9B0@S9yqY~5}b%e2NZ2* zdcUpjMVjzay+jlxtt=eDGb1NzB-~GVwp_n#C!^R9wMyq8rz!ry?v%?@Cj6-(brvFa zCn6{E-A)5#a)x8>ba>)I&-=dJ83fy9ZG3zsS{=B`E<@ElzM{K-wr-Km=hR$~UObb; z|8gb3bWy)RhAEYStHH-V(eLI+DU9a&mHIQ%(_xI{(E|$9(ol?3bfulLnAbRym{fc; zooNvp89^{wiPdo|S?7rM&N+x~OJb<5{F!a%&{(bIjSh+e5}J;?h8#3PuMGq^LVtp? za`94Tg@zW5Q55Pdbt}nMoF)sVrj|={hop$37~P`FT6%*qIttX<8nP8LJf)FuUM}@^ zt@=A{AXY1NCJV70m7UeP9l67KN-Mi6U-LthE;E|soY<;Bb#fVcFF%LU%JZt?n%%gt z!TqnLTb7gseXqI&D=0%_RFO&Hsz_ih<`f~WYI7pBbK=%ibVF)LA}V46g%&+i2^!;x zHzhG@wLg!bI-9Lfvm<+AY_2WpD>D^K+Im^FLFMgTRm{EKE$1L}{l?Mx?d1d69L9A2 zO4<1L3ryVJ{4Y}NV*CZ?^CCmH?2pG}sFKUO;bRm?HPQUM?&6Tu#UYKZ&8pfcWjPbI z4Do~pY)8{XlAOrT^^HMC@t6sBc3f|fXStE!#w2*&#^B4OHP3*pw3nXr2(LuF_+6** z`SOu(aSndkD-&#qb>%HM4wf(uVA!}cQ*PXoXaNoVfo*lUM<*~*^&w6(at}s}vymvh>GfWeb#-dc2!Co{CgZ3~npY*5 zp_f#foYMglr%U(0`s}d?T0y!JW2{4Ct#wrU3i=9K&{ChmRkI{^omOmv>n-?=a>J=D z_#mc7fz zvhKS|S72NX>X&y`zK$KpG_u8&BTWDXXiOJ)EVnua)+DU^#=Y$FWg%0{Wx!UtAI+5I zaRkPUM9g-^i&Q5^8&CZFi#Pi(&YIVQ!*#HW6Y~S6S$oZLI#N6z_mmtl=qn28O>8ne6qO9RBu@XlN7zn#(>sjw09o@TH083aP&!Zj zNR({49b+|jgRKL-So1=aHN$SJ-n&XiL=fZr{5dGHdrb;Gva9;G6r3NbppXB5_EP^v z%?|@;64@SAki=SFtd<#q1s&(0e7M*~2yC*MuEOgY(%Aikp>w9V+V?1Me|9BpdML`% z3WHr40!G9p%n9O(IE>Lxt5s50-~U|0Rb1xbIKx$KK4$t9O=aw{v!iYb-;=rWY?P{N zkU^F$bXLTJyE2g7q}I~2HZXsp{A;>M&ERM{>TB09nf&D9xA%PpWg6*U-Ih^bg|Y>+ zU>f23g3p2H!h}(cN42i}%RBp($c2k%<6pReV`8s(yu*W6xRLx!QjXavx>`f3trCV( zinsSMk*d3V*;bmo-LSYtQ*X3rXi0B&anB$1%dXky=BYb+3kd8muThzvNqFyAch@+* zeq*y!s>RrJ%IwI2apRs4S)mts%V_buHlwf5>dK<~VZy2UqE)IU&wwkdO{@Z>K|Uw; zhYiN>E5b2|X+yjRhDV$07EHG2v`9Ckt3rv+lr;q7IiCkv!N>$zGUh!UHJss}^`dGS zD%O*GyS@B%*;h>A@%s;p81rz^)!w<`&^;gj^au5_fp2{)f(XPh-TDrOWm^psc^$Pv z`LJBrif2b#<=7^LRNc0A(w5=cmnK_>v@8Q$4qUz`zbEG(F#c^qv66|2E7DV5w1a-5 zIV zp;2+o&x~~0oFkiFwo7AZ>C;4MKQ$X(_NUcfO|ta5Dq;Da5ob!>_0mr9$sIF!^M#b} zhKsl<(Kr4<#1Fg{dq+NNXJtgJsOQY{CJ&^ zo5GcMD03Eq#pd?aW5qce`>&88J@?st&q1b{9vmHP=AHqYN2wvtsu;QxNJV(M%Q}m8 z+?MyWXJ1W}r-*OQmW?p17$)pGy`9{Cgg^Y`&$@t133`=rSOI%rb7Gu`_KkpZ?YVg> zp|Zp`1Cwquk=GRwns10ej9kkKAbpi~ZLM;H;(+)!?mXoG5_9ZM-3&R%p{-%KK(2*e#QdKIEhi!l{b4s*!$6il7i;C14A{Zq>>7a z=DL=DM%n)quju^hTMsWsXBi?=o6jqG-eIq@6IUG1^<<=BdPaA`1Y0>|HxM_Jitb7r zlaF`2f_Fd>-sEq!raR6cJt4b}V5)smH?^XvhSY(UIi^`r&({Qy^L9HXVDqALlYC-a zP1fwK7-g9Fcs&t$Mt+k1mbaibItHNe{c_v0}JS?9N&H2(y1l z&RkM#!>j-jBHglBJ&>_e*nVP66(Wsa78=@M<|E~uy&jIpSPQ2$9J^g zb1jqjXb15Kx^~AuTP@}U&Ym63}Y#&*(d|nn-)ckhFn-KK| zYJ-h{+VbgGq6w4^^_RH>Yc4$zkV;+59xA zm@=&yHDfciGP_wLZvX1o{A&XKx&(z_zF=tFb3i?$(u$VYww;IQ32FB-1wxKe7`pV^ zJlT?k-TaR+N7P;`erFNgAN@s=Jqxvw3J4F*tdaQm(UZ&kAB8rqi(-PjDmyE?zpX^M zeVFIFY_#ud*aR2aS|9FCEj(d(Kls;_03`$~Yr`d18+DG1%dI*WT13^GS%neOLLn)1 z2aIdF14kl9n1z$Db5Nh8r(~{E#?D!AaYX%Su}M*(`!)KPz(QlsOoH^jtj=@>_SMwPX>!6St5mv7Sh;v&9uj{SRex286w8?ic^n$crY+c! zuOr`D;bQhV>iKjHm5vfi@Dgl(99SIF=2gNR^&p~)$2pKC%@gm%jR2C*GURr6&(JVy zjPUY6_dryQL6GwN*R(vaGHUH~#PEP$E6wv1sS^7BUnTgT5JXI@AFFnpX4lkABvRNU z8t%Vr)LPnMtlS~XOt09M$rSDCwH_y4AcBmW3r|^s=V?`lx>ZNZP^D=2&cq=M-W?Vt zp4HtlDwNA8dc3Lkem9V`$SUZ~2Vv{+(di)YNB7nRp=kZsOoN6Z;g2t4O8$h3{|HTt z4@Pt9T4g|UA{HmAbF`W)89T!Q$nB^i5o@T%$?~JKR6n6~qt%e|`C6}@#p}8@rq3Q1 zw)I}mWtl;a%qfkJrB0A0bp?e1I(0iQx6&kpFS{Ew z8|HE$(yUghjzl{mJN(=ZS?5HTmU>U4kc~TDB&F`=&habirq>j6=zX;d<4g{s=-PYD zXQ{qoH6+`;-l6hP5&^M8vIC}L11~#tFARjDO$5YDN1z^X}d6qUqlj(#?sD zeafmFi_pr~1m}van<`8?PP@?BYxh_B+9+r*9G9iLeQ;6BDpzA^fne)!yb{=(TFbZ{)VKe~=5#Q`Xo?zuT;Srds0_=Q5R*$vY(=8|-DTcM6M( zN!VTOeqH62n}g!X_1eSaL*Sc};k^F0-T$skOZQkbKPz&B1ko={n^qn_Fr^fL`Z!38<#DG$*P z24@At?Mx#o;mwPxI~r|Ak!9?HjX4Ht#vJi)x$s2ZeIGjjr&LS9yDGNo6HS8hiNU6a zhw(4O167nQ9L@k+T0l3wxNxN@LxOh%Z(w0MBaFnb!mUVR-Cj2!=|ZyKUN^WP!&nE>dbtF&OMhjy`#P3%&FrDTocl9qG#CObD*9E z<95s2mW1rE$_UkF&djYvqa`c6R-rlYWv5|`2HR^~$dm8o0554FX(%$23e2?-ajXr_ zYB*T0ZxlbK@zwW)xi_#*CfL$gCjk@Z`48d~kd|IL+Y(;dh(32{IrVnl>hdCptNWTv1#zJrfO+ z@YQPHiIYyYyf1r~r3wQZ>YL}SCjvs#b7uMu#a28w4upbQq0k4C&<74YrCg<~lz-oK zk%{3cR3p9U@I|k$9UnZabz$9a+22-~m|2J~>)SYEsW})wv-LX(px!5OcJ;|#>BxZt zgROA)Qt9^&Gj126>m7#I)=C8mVRC5>8b1-+4iB2N^$PF)Hc`Bi;uo-1A0%FOMi}Eg z#-w)4SS5Q&x3u(yd+Cc20~?49`0s0TI0|qBXc9Flh*0Fi#n1ZWyY)sRHn%mWNMG>d!d`^O%T)H_3-Mpk<2!W`>?@KrU$vV)u(vpjT z6`!eJ52QP6tlbbsH!f^B4epu9oP)aFPjs*Y7T=t45vAzBX0I#HXmWVXVtLJS`=K;Y z%Fy2&iQm`UAD7aCyN)v4VYZ)?9P}&BLFhfvcUzu%{G54TYc0w`J|~|+dwS@5 zdal`80Z#_U>!1F68~+)GfE5}Sqtn%q3&G^aK3?&M4#XvVItMi#Ja*4M>C9ly3o@K% z;$MR=8qsZ`w@x*(5eqJEUhGaQbwwXekvfIO4#&FrbMJ7dzqi?oC;AbvAVB+ey;gLA z`Qxyt<3q%(=QBteBnG(HN_V6yn8KS=JBPf%e)eMMKQRKafn+9nKZOOaILMM*Ynv?C}iAnT$rR&C+p=t$%G6AKct| zU*?hm3OO6_KuK?GTcX@Q;V>AilT;D#C4X|~?xpXRV`TS{_cObCB43EOJ1skbv+6#s zR2OC!_AZ#MoP*Yc%fd6eYs1SnE8S$c^}a#TPE9P;eH;^w1C7JQTm5mbJK|!h4Glbg zggqG$V!VI|&ryXtXmkA>!ii)U55Z0!eTaNHf!T`fh&a{Ab61@Ysn=W7gx4HXppT2x zJ%gBYv|`zc(p|ZHgrCqC-u*MK{wx018J{8AP0%Ey)G2K<8dh4vza>l5eG(bk8KhmL zb((hFcviURHFCQuJ#ZrRpsyy9G2zfw1q_I+xDf-7o9@5-{pGt5+=Auiy$4Rz>Cg3e zSvPkMVp(J&9_{ZwAg$)%qYc7X$TysWlB>64CzN^*O9BWQ$HoGvo+7tkCjm~0ohQ$j zIy&{h7+yUvYX{;<`#}>~s-*_(&Ox0$=O8WKnisM>mOXJX=$srDyZcM`7w`V3yDz72$Lot^azb-GJ4p|%3};nU{T4%bc$%WTft zPBoZJ8rF757IbyeQjhiGZz29u{t5iD?W_Ja5D{b1vd1k)J zniqHEhE>buSATF1N?)*b*eTvLE9B*bsemCl8CGVNKVS-k#m4j%UGp~9E(ou9;o4+irG_a{Z5*R#s+u_VfzVk~nzctJYsgsS; zdOPj#h#vW`qx`4;)i~6+(zzdOe01^BQ=C~kT@2sa8;W0(tfe2gl}hspSu)V-r&uq3 z2uZ(^w$OQe?RfZ6i)&u;f^ep(=8K*6E#aJ4UQPuQTCE@O)Z^Z!;OdLDhEgL8AiP|0 zA`gWSzDyYnYU7@=>0j!M02f464T|kFN?{s2zfxcnXpQY8^*dsn|2o8k)hfY&TO)Gr zS{M9jSN}ERiGHl``t+hd+$hXlmOn3D94dziQgqmichBkiI9E=Q#dU)G@NPYHX!@g9 z!HM+!nzBv0Ons&F6Q9GRUj9r8>eh#G!Xh~|nO)huV9m#WSI*HObTuEVNI-MgbaY`s z1n&p!9hMd`@K?sIu^ybC%%*D&)1JeP5w^K$rqJ5(tjY03zj=`N%_rRlz8;zF`pKnR z@yHe#qje*g>=mBvfpV#;8O2S7I=eS&5bX1M?FDn8qUzLWzL8IX{EjxG3VKToTNQg4 z5epn@tIc74&Q?9NVpC^}FC)ayYwsT9(M&7%+pp$;(v+hN0a@MsSLXfMe;hMy6~xBC z(|F-Xrex}|Zyt{AZl*U;2juK&Uq=M?nu1DJHE-+0^QGSptkR}ws7c9`WjiBXcc5EQ zU^u0pr=p0?TQtVYdGCe{Mf;9HcI7NiOudSC#$@O5)NK4|`5IgGGTU{5&=&p3Jjb&# zC1z3meRGwq!?e&SL%7wKDZ=1}Nx-BtAmQj>)Y+P4sqW+cu~ag*CO=Nn4&0Ruo>6%j zs&eDcsQ*4ZE~`%8i(j%^V)=QNh*meQkXY}~pA9g-$rtrQhCynawi+^xZzGt@ZQ z3fId?5zblY=_|54^ntf}poOgOar}!tP3|X;5zd zQ^-z$jHy?x6E4}+z4~x-vn`u(TZy+bEOc6-T${F>@Imq5;R%*Y}>sr~N zF{e==7Z%CLR9lk1FQ%C*wqLoi>2rb!Ueuz?F$w5{pf8JSeBxJE)S}X&xJz;O4|@K`C_r^hQpoUGNIj+r!Ba8MFSela+tfvwo-)H6I~|Sd zWZkCJ`Qw|-i|pseD!sq8r|J!n?=@K&AO+Yx{Cs(h_iMGyAG30{T!8WQN`d1+_uYp7%NsY9v8YCaI>W^sYxi8kpy5F7 z=DBwFqia1)FDHh_<9$(NNHW~6*MHX)NRw}=b5B_d%E?P=H1W-4DZ<1nV>5N+Ktk*) zgkfZTWO|&ZCTVi@07Vp1z6ixkrTA~nbt;|~xJI2Z;=xd@I^=KE0Q1`?|3%>=)_l?Q_86?>C-`e^U1U^X)V8V*EHKAd23OT5- zUuc80r@MaJI|mVW4z>Z8Qd0>{yMS}hkcQ(-`a`(Qk$rFcO85td0d!kpi(9&16UWQ5 z(^ZX2sYc>Q9s+(b+Pt>8USFw8UyWC4QGscnmi_=U09;ou7WzyAW@!7u<8D;fWMK3G z-i6f&#R$-g7z|m&K2cM%&aV?HS-G} zi^v(jHB-pT8x8di!w-GKTT)@Hon<-PLpCT5ZeY@ zJ_lugEWW(P*~{b!FH{xI(GQ-={bEd=^8<_rq%B|Gl7gejX%cL&Lay!C2K{_ppkZNT zfGf;LRc4~npniAnY;a*PvNYgIJu;xW4e?U28OiLbp;Xr06om&k(fP*_m6#<>`NzU9 zs+kN@*;D5E7LioUra{jSY7A0pbLQOrs!~zerI$qo#O(oF81IO#)4W`rs5NIip|^$1 zAj&^IPtP#0KO9_8`n^2{yT`HoMUhDlalttFV?IY2J)v8sR^f*ShkgM;ivx_ljURtke zPX**G*k=QIe_E&y^I@AcNtpQdBBrji{$a?&()(Ta`<~wYyNlYzBq6YZ#>YbGA(s#@ zutG)nE^9%%)tzC|mPXw%n&>^5e6|Z?R7;f!EvntL_(Zw& z>nTj8^Rj(Fs8sdO1}2dFw&Cwpywx~Vx#-2A7Ia_qtGzaLiw0(QH9VVT6Wr58ibr3B zH^6s`wsbl6=LKf8ETSsyWK5_a?eHaJ?_upp=1v_426;-le@K_ftQ@@f;tgp}6=^Is z=SOU50iJMFl)Tq^XQd;WpZX{(%2jM)aOJw*A~8CLM`xyefz5HDcELvS!Tk7)KSJGLL1^(Jsjd*1}fEktI=N5U&dPTqd+XQC&97KPx zrVTdDO)Fvw7v<>WAI@(PJp0)5x=R0esHjk0ESJ9f+A$E}x@&UN!xoBT^YHJ=fZvuF z(!P@0)UjMPIgh&P-npzP?Vs_sAg=q)1mUEl?KkGW2|wa%G|)~(_%M{bJT1f?UlWQ~ z?Fc_+mN*)#c{fy>|E)^dR_$Yfh)G)YH94@4pKMdLV^K(Fhx>S34UWi7u20|P|HSVp z{HiA*QkD(Ze9R@N(bXR!l(8>PXr08M=dIs5?h0ZL`Vo zHKISf76}o646`*}lN*@V4M?;>xJS8tDs0ZunCCdt2%ue3E(#KOpN{LzG}jZr{>LbwT2{JzoPeP^tw-~z?IRdz4!z5w+#?= z=m+FCjTM_?lW&yu|M*j9aOj~j$$PS)} zC5_~DSy-P~^HZXNLirXJ(GbnT5R}%d`L>iS#pYLDRq$_8LDUt7uY0b?Lfl+*;Bbw+ z*~lo1)@a81YYY1*{&LDBu?;Sl(kI`uJHe%8g}I8YhsE?`F z$(toxd8ZXCxY%AU&Q*K0RxgLK zyh3;l^i}OMQ5dH1QPanrikhJI>4`X=UD=rKtIK*Dd+W`*t4PQJRw(XFY0*b)S9&6J zRfx`c;0{*RHI6$xCWz{xD!eo5Sr6R5RnkcX+AoPKj|BZS2l0D?GXg1Mzf1&~IHi4T z?9~kK(L?0E`?l(@mQwr7b5ShTv~H|nZgO}(A^zoe4g4X9TjsTCa9H7_JoP@oZ!qbw7#`ufweNN47nkkh zblumSyV{9BL#wmdq(%Ca6|K0swc1)fA0zKHKJn1PMQq)u(%5JjV^@+H?@6VtA?4*> zeWyP`H^G+tLxKSXiS?5|Z1aDF#6^>Vvh(lsm>Ily0WDrwmb3d=n&sd!*O*5@)4Jfg zl3-->H&sp@rBsH#!yRun{arvG|dNrK(K|bh~2fZo#j)}L~>(oUzFCrgC;<4lF8%#5XI`hSdGtBf#IQ!WzEjl$~=%73>Rq}ff(n` zA}|X@&29T>gwE;Y*>`qEBOO{~`F2tFYtFvJKWURQ{dtt7Vd2EMVlmau%P0QbP~WRu zQ4JJ(Jm0m@sO$jxw?(qdRE`z4$!;h$o#k$mfFQ??uaWy~W=+F?rBi4bsRjyN3Bf={ zFz^wKdbP)~U}y{bz*ZRwkd&L~I>g#U9v-~V(9>rh`7$yffRQYOX=~UJnP)tuMB>8m zfC_R{4tMBnL)*awBir8;AG$xdBzv}!0xYfpms}QKSytcaiRH8@WJ@q0`4KAWe{m87 zqTrN&iU2SD>RDAklQ=2lvNBrV%G1N9q%Ah;zR5j{LKnu>Fz@jiGh;^Fy_6iodL*UF z$LxH9C=OnJ6Zo(<+q%4Swy5sHuUT}Rex?_Cp!*mK`3pM@Wv!gDIj=t2bJiGaQ|XE# zt!Yfw^`JEJ;VyaKK4+KRlz+b+6!4r)eESu+)TWo(!GVf=1FVq-z{kN#21 zA6$ubilUn)#*KoGqfVP%hII6^7oGs|o~8`CyQQL1LyR>lJ4zjgtyjMJor9Q5g5np3 zQcoj|DW`^9S1UqpCE8xfw;_A-Jl?qw$)u8~^n zGp-H|v+4U2E2;{e8WhZ9#kqto#G%JGD9`o+%k=QqS^Ek-HF;gf-cNQB1 zZ+&vls)|$_>uGEbod!n43yuWCg4`0Fg6P2I)WTLO`uCja|D>J&A({XPHi;tz4E@@Y zFbQuMNZ z*fs5Wlj1EYUX`(_4@`t2t1?o+mB5|o=(Xm-^5%iQ)q{uk+%qYE^nBpv^EyO#2a#zApR{qo`~R=IfJxrs?61-;ivQKiwUmm~X~O-XfR+6$)L&LfDxR()%9 z*}etf%}0+-&&tiO1Hm(;nW(K;iEFx$zg#QzW5I|P_P~jZ=@dW1gHiHM^;gC!H`s&Hj1@TsHr_76?c8!lf< zk0@Wzd|q}A(%JCwyydBPqU@(Z>233hiETdbW%(5BK}X;vSMS?vJ4ty?<B=W zy4jSsp8VS&=co7qd=X814D9me>|I6e9mm^L=OAgRwgA4$dVe|YEP*~>Z@=0xw1|I% z#kaudLSD*~iF*?8cvp)A`_rOow`)hWcB*Rd7kHwTapdLioS$%1<-qE6avO=^9Ikhc~}+r6wO? znQy99E4Oyf1%Bw~ny6gxGNE!YtxJDyT4_C&yth>8Ss{BRGQwA=L)2#7Eq~gXLA?=L z=3v1KIX=e8AG^FJT^43oGG@5G?rMV!I8|q){AP#@*dHF8)#kn0%WL}@crih~=;3nCTSRVsEC3O6Cg2G^U`DlG38 z4PydpLrjp`t7&5uV$zwC#^)dA=hgch}*k692BBTq1Cxj(p zx!Xz*Ixy`W@td&N9&zr#)De-h@LEYNzyNH=oNQDHqfSuQ18eF{7aG@YEz}+-OYB+9 zuJ^V1lsk3zRJD|@PpW=2ypQHgy!#yvEF8)bq;(HBU=l8S>Cy|+W_(epp<5uwe#Cbc z8+DwrFH7&OusAhLnr6zc}UrxF695lyIm&l*h;?Qw+S%GqTBz1pJf?EA-z*jU^ z52kY0NS^ie?`F;40dXi1K+HC2UrV{IHp6@n{8f=gR?RU}VuNSYyoJ-@n=gat_>A_K z>4%l$R&M-T0UB=8&q%0kTKIwrOe%vaSQHyHP^&@V7jgJ7FaJbcdDU{myCk}x8-Ga@ z+fRT4%PBd>1@&|C5jO?0AMqrPmF+sWJ#zz=a=X&6`=U>KIDev8+Iul>1A|qpZ zXE<@&xAJ67fvUDRgdJ(Db98M#puj=CCYgNE1c=vsqnl%8W^MDAZGT^Z8yBHGzlnp6 zv!+-T()pmrf_ce5j_cF!4(mhouRL3ncBoD&bKL54IPE>QMe|#1*~I^{*`0MOdC*^t-Yx%|-C1FO z>sZ`dpIO1R>1xgSTD=Q}+a~o)v9#3zMfR=~ta;4R50u>$B^;Zl|Nuy+5^wRJ5!ZHK$xY5N62o&~rfqnkuS57Z+nsb_`k zq)OYYX1C-uu-Anap*s>($@*a z@I7zK0FH>^;uXy90}l3hCy&*Zc$cV^zCW>s15HJS2jFcK1XRqWht{10c&yA1pjRCI z`~=tSKA-W5D}`f=R^{-^j*onH=Sx_mKb&glPnHq52S%Yq3)F^pmj;R|y9c&_H+8S@ zpx?SrcZeUY4)XeVt=L=^`{jFsRYGqX7)2b9Yd7sZ3(GHy%uGNa4K)2ysoEC0D;~h& zIW~OTMO2<;Z(Oxk$jA7K6pm$Sm3K~1l?`l(@T)CZk2SY-JNcjGzTw-iyUZz^S_t0| zvR)P5<+ZrwaUxUnCKMA<2GKFjqH~;;@zY<7w5YtEv(k{HAi_`C#Oe#T&F$72DhY+& z)_o(GkN~6MO~IW`Z0Y9A_#5+=7&w>01}yUW)s#ECgix?`j+>@!KJaQKSG(b-2$44; zLjB{qXeSC|_g)H51ae8!ys0+KGS;97lF6sK)Jq3P6y?zJsQI3arq0}TG0t_NQqvF1 zB6lnb%${u>i0|7e#QStcKAAPmJJPvXHf@K3ZNwQei6xmUbGLq!X>?3)SQ_w^C2`u{ zh3*clBXdm>KcC?)T)JGSyJ~=#We#qz(=$b|^siv#6;Erm(4H~@-8)r|o@F9bj-D8z zgzt4o0>b+tUoY6W6$q@BV^Lcmr+Ag8O##Ptw&vOnWVKRBC@J-vhV5@^u@b%62no9s zz<`}xp0z!Jj4q&dqTY&D5V8|R6hm}T!;Qm;bfIh3%<767?LEG9^5*${wQy{Ue+nit zx7{Xm)7@p;$p7t^!zQi?k<7BJjb~xp#hstV2J$xbSL6@<+@E(UI&Y7$WbM3QDgvJt zSXI=@gzP(P`c0)&CzN!oOy^|g3g~W?>WrvmW;fJxq&t<{BoZd(8KmfZc#oWmrAo;S zehwqK`|nqxKgv~n{KnYQx`MWYA_bXp3$AJ34IHoX>v2~VZF3mw>PZGQFxKSoq(!aW z35Zdy-49SJ)qUABSh1LO1-?66EyZMQoP4ReM)goIDap+FU}S)^%sjlz(U%rD?14So z*)NQ#(0e(07WL-ScskqY&Wks#j_oz`Sql+(nRjB~W`zbA<|Oo3yrs13T*96*6Xf$5 zr+e%ebTf8~8Zb!s9!t7L@%Qhf;(ZPvfBN#34DI4JchQNO$G{L(RDWnE=R)8Lyf97O zFttWQd7%V2N=opCH4ggjUmKNw&kTI9+jG)zIXUHps>^H<&qlyygYI!h`Q zX9v!#q*vG_=@V{8*bgyEo1W|&~-UK11M^BUIyfAG9< zRIt%vSn;6GmgmBPmn6?{rig7WR`^KVU(V44>}%EOQ+q9Xcix|QI$X&x4Bj|e?ORln z%fuEYx}&HmBW1R5)rgR?k-8fmE}Fk%Z?I>dgkEtytULw7YF~VDv-;|2Y;ZftF*HH( zajxrsJTZRb7ols)6mI6o)iDeeJ>&~kWBx=sQMg^=SthV1)Lfp%ATrs>JyB8wArTs; z!z+6l588#->5N!EyJb%BI;Ph@wzu1}+AH6|Z!z~eRdd-IG#+T#G%?&;pR5*fTG^L< zGDU~uEf(<-v5FKnI&p}!@)K2Wt16CecVDMYf_F-hLkg@UVEMjmVZa{7a|~lRywKvV_HjWiUX9Ap{7r2@;ktAo~_id*)P~Q&V$pX`T6S zKiujM-Cg}uC-2+;dbWQ1cUN_5d==aT#wKylt*<&ny}2J=sP2Rzq%OkytQM7Cuf(EQ zQK49A*>zT`_Qb?)#{)=RhU>^mbKGp$_;{+BUxILnvWWKNoI2Et^=v)4zOQF{5Yu(6 z+@ZgyducV@X6A$~SRX3w)sOe}T=dBudB3b=y8q&zFlD}FK7O}ZbIfT&m%m>lY<@~P z&3gNVNvy5+5w0?-x%W)XooNY=>fzg6jGI>|%i?C<;>gC-KSw9x>mz?t6jvrC@^&yZnEX^QFPDB$#c7pDUe!45; zrf$#KcomDe6GqGG*^mbCr&V7b?`%B4zKvYn-Yw{s>xU@Am?^ZO3hS-6hG)LWZLDvA z(-6}~Qm-XJY+PQJDA4`rG(Zn=cO7+Y-f7y@mc)6uLkF(tC>fDemGb|0J**E{>qybG zI3s}+EBziR@@b)QnSCnhcEw46AgJK^WF3}yVn7Hr%qq36*=(}CC{>5b;|(lV(o;(t zEj=$WuuD60Du-Rk;S47ItRU&ViM(>zC>vD|X9r9l-z}1^7B@;PH{Z6Dmj~Za(RX&s zBV^*|!lIw^kdOLPO&W?0;Z$x7aAS^V!f5GlJpjlTT6bJbjocUWO9Zp8XKyCPN*VpW z;mWXh{^fz5Ad%8iCAu-%uh%?guzbh6kK$5PuNgT|-~d1aBU%bCBo?UeT}m=N+U01v ze|_M&y%*Piy8!;w5@?iR$S}|b@Ze4w91WS=d?%vWpCx1%Ko>l>jJg$d_#+B}RvU$a zf%21X;Hk4TY0w){fU*M>zru&B`II7ikIz*=bhna9psuVgPVkUb(Ab{jWiN;Bcjj|t zFoz!{B>O$7Qdqge@N=2PprnDknx42)?N7|ZEv67ln@3^ZaVzTAq3#}~LBfUnF#9mP ztOFcjsgmATs4$5xz|>7ZvfuO-Hivx;D#FoJJBxnzw0A4bLH&Moip#x!h=Z6`ccFIK z)}L}XtC+vPx)>c}nz)&WJkoBzS>dNY9kyItnoScK069L@JPudH4@P-sdlJlhZR{7fSqs4I>=mg^Uc(n3@J}To!%Vqjh#w{`I!4eQexj zR~qBy<~Sm=P&ikQw~Q+6EzKU4-6wyM(;C~CNT>j$lW`g*WGX|);k;fozFkW;o8LN7 z1~yO;{*YOrC$P5)BAw)S9y@^&zaK=_jQJbtCUYCp^&o!6KO;5IcaU25%LRq~MQuZy zUkwBoY+liobqsg343#mmu+7u@%8K$}0R=XR+?(l{ec*=oM4193Auwv)$>PT0B`h&Z zsY8rnMr7Pfi_59v9Mn8JRLbp{Ti`|sFsu+!4GKJNz#QVXPnnG}h|-VxLWy_2?XK~W zgrcguFTcqBII>mimoSI{rAVa!K4TGO?ObdI>5z}OGB%S{Nj?iO& zO+@O>6O)081bz3qRof>`n=#{m`WO(%`OKSxSG4~ip|v0=5MKmGNkeK(Ami`@kd2Rk zbmK)E>#J9RQ9S{#Qofgaq&$1Ty0?^Hp>b}&Hv^-OsDWzSTQUMyRiA&~f`Sp}WP4>A zBUq_otqfFksSYtXB3e#amkoSYWD|tfkYCVvov+%h4@=G>-+Oj4FUC_*IsA_v5QvE@_ZVZxl07@Wi0zDv6wfW^34pHD4 zGMmrUi?WyYcX<;DN*$DO`%3$oCL2DY`|~|MUto|;EWMXg^%Ec%C?FV=+mdnj5ZZbYhR0*@gN1lQq(m#21fqIl)r z-d>z@O*6w8zl>yUnoN?n6orcDw1hXEHKu&=v976!z7aqi z1h&mz(XsZZW~YPGPU*j~qYPBl@eDt90Dw*Xaa}BNRSXxOo;sh)8g0?xxT8?RpUsX6 z;DnCOxx&DqiL1j8PIab;&XVUBtbso_|3X!6!I22K7&obSCHOJ04)#>F;pJ9Og}(Z2 zXf{fS)m||$lT{JE$hkrqLT*k}7JjxW8qpfZ$zlfXpY{+^?A?mO@w-Wq(y?WD3QxDP zZ}ND63F;D6F_;|HZMDyDw!R~?TBK>wW0k^+Oy*XcM}~d+IY855!%NLeYmZR=7TEf& zZHMpD(Hb)PG{?5&ma~OpR7ZCXnf@-Hmd*1S^v|dCscV9#0$x)PY{!6yz0De{00rN7dm)FC5E-|fT4eTYd@*P;@URTz>c6N=G zROYiYL~Z_*?wxK&2?xd=V32Hv=65eAlxoLkJ6jBEDu)kMXq@p6^ud<}xN|CH(a}F? zFClKd?g2^b0dS=hRk4(&O+IxqkZM56O8&X%YMZmI5(@ADO|<$w+Bs?Q)dNU2#);W2{d;(zq$0I9a>ft0lE10Z`CU)?-;LM#-g`W@ z-^By@%%$mvlRhNVE}fhq6;!DgB10yv{+|&~palI9(auzK>1W}0A>rBAB$CdIn(63h zZeWj!&-L+?3!T%|lsQ44qzIV8ijxoML!bsesUyzX{^ z!cFY<%zDMV>L8nb1_F=ZC6Zc!C__i1E-g#})TO-3{qm~aTCc{Sjh$7od6Q*&H6t=F z-2#E@N-VNUWRxE9lpF*UxW|a8aeGi{Dc5ytZx0md8p*8%UGZ$&NaX{4VGZpfRABu1+uT|XHV{_HtpHj3r6Y?r^wwl{GM7Bc+B zwkoIuM5i=XH>2~TK|o`G)47lR(k`lN8m-xc-;{G-4Q)IUH!iusH$5=eF32Yh%E@&} z{>v|{{{0#L=i??I6bvhNJlirBw@Q%p2GA$(+;83x-(G%9r4UFtcQ#ma)qKV=AMV>< zFe+Qf=S0XWEGXuMdZ4JOZ)N7|uAY>>n6D2m-E|Y$w2*-Pb?NqC%i_}VsR!|I2>K=l zIy$Uc*xMc^jcKOoRJp>@^rmhLDZl}4^M^P@syTQv;6fi#&vws?#nixI_^uf+@~O^) zz?tJp#x(`M355fO7uhr`fGfLzQQb6Fw0XtML6G@GWWuJ1?Kv~?mV#tv zkB`{+Bft0tulyr9@=llYI&@-{UEvc9&uNa(3&L?GTf7WSMjpLtw%{5c_xF^pdGg|} z<*Zr0BXi`#jCN<~xj8%0%TR~gmbz#8$w~!yhLzhMBbqZ>ohjfe8XSd{ZO?WseUpl>%P`l6;3?Imj7eA`d5tL8}6aN!2Slw f1=c#OGjPQw6gWQnzJ}&E{KEg6{2!cwFP(n}Gg4qM literal 0 HcmV?d00001 diff --git a/Source/ATHImagePickerPreviewViewController.swift b/Source/ATHImagePickerPreviewViewController.swift index b622bf3..b10fb5b 100644 --- a/Source/ATHImagePickerPreviewViewController.swift +++ b/Source/ATHImagePickerPreviewViewController.swift @@ -10,248 +10,248 @@ import UIKit import ImagePickerKit public protocol SelectionController: class { - var previewController: PreviewController? { get set } - var assetsController: AssetsController? { get set } - - var constraint: NSLayoutConstraint { get } - var floatingView: UIView { get } - var offset: CGPoint { get set } - - var isScrollEnabled: Bool { get set } - var isTracking: Bool { get } - - func commit(error: ATHImagePickerError) + var previewController: PreviewController? { get set } + var assetsController: AssetsController? { get set } + + var constraint: NSLayoutConstraint { get } + var floatingView: UIView { get } + var offset: CGPoint { get set } + + var isScrollEnabled: Bool { get set } + var isTracking: Bool { get } + + func commit(error: ATHImagePickerError) } internal protocol EmbededController { - var holder: SelectionController! { get set } + var holder: SelectionController! { get set } } public protocol PreviewController: FloatingViewLayout { - var image: UIImage? { get set } + var image: UIImage? { get set } } open class ATHImagePickerPreviewViewController: UIViewController, EmbededController, PreviewController, Cropable { - - // MARK: - Outlets - - @IBOutlet weak var cropContainerView: UIView! - - // MARK: FloatingViewLayout properties - - open var animationCompletion: ((Bool) -> Void)? - open var overlayBlurringView: UIView! - - open var topConstraint: NSLayoutConstraint { - return holder.constraint + + // MARK: - Outlets + + @IBOutlet weak var cropContainerView: UIView! + + // MARK: FloatingViewLayout properties + + open var animationCompletion: ((Bool) -> Void)? + open var overlayBlurringView: UIView! + + open var topConstraint: NSLayoutConstraint { + return holder.constraint + } + + open var draggingZone: DraggingZone = .some(50) + + open var visibleArea: CGFloat = 50 + + open var previousPoint: CGPoint? + + open var state: State { + if topConstraint.constant == 0 { + return .unfolded + } else if topConstraint.constant + holder.floatingView.frame.height == visibleArea { + return .folded + } else { + return .moved } - - open var draggingZone: DraggingZone = .some(50) - - open var visibleArea: CGFloat = 50 - - open var previousPoint: CGPoint? - - open var state: State { - if topConstraint.constant == 0 { - return .unfolded - } else if topConstraint.constant + holder.floatingView.frame.height == visibleArea { - return .folded - } else { - return .moved - } + } + + open var allowPanOutside = false + + // MARK: - PickerController properties + + public var image: UIImage? { + didSet { + guard let image = image else { return } + holder.floatingView.clipsToBounds = true + addImage(image) } - - open var allowPanOutside = false - - // MARK: - PickerController properties - - public var image: UIImage? { - didSet { - guard let image = image else { return } - holder.floatingView.clipsToBounds = true - addImage(image) - } + } + + // MARK: - EmbededController properties + + internal weak var holder: SelectionController! + + // MARK: - Cropable properties + + public var cropView = UIScrollView() + public var childContainerView = UIView() + public var childView = UIImageView() + public var linesView = LinesView() + + public var topOffset: CGFloat { + guard let navBar = navigationController?.navigationBar else { + return 0 } - // MARK: - EmbededController properties - - internal weak var holder: SelectionController! - - // MARK: - Cropable properties - - public var cropView = UIScrollView() - public var childContainerView = UIView() - public var childView = UIImageView() - public var linesView = LinesView() - - public var topOffset: CGFloat { - guard let navBar = navigationController?.navigationBar else { - return 0 - } - - return !navBar.isHidden ? navBar.frame.height : 0 + return !navBar.isHidden ? navBar.frame.height : 0 + } + + lazy var delegate: CropableScrollViewDelegate = { + return CropableScrollViewDelegate(cropable: self) + }() + + // MARK: - Properties + + fileprivate var isZooming = false + fileprivate var isChecking = false + fileprivate var addedRecognizers = false + + fileprivate var isMoving: Bool = false { + didSet { + if isMoving { + cropView.isScrollEnabled = false + } else { + cropView.isScrollEnabled = true + } } - - lazy var delegate: CropableScrollViewDelegate = { - return CropableScrollViewDelegate(cropable: self) - }() - - // MARK: - Properties - - fileprivate var isZooming = false - fileprivate var isChecking = false - fileprivate var addedRecognizers = false - - fileprivate var isMoving: Bool = false { - didSet { - if isMoving { - cropView.isScrollEnabled = false - } else { - cropView.isScrollEnabled = true - } - } + } + + fileprivate var offset: CGFloat = 0 { + didSet { + if offset < 0 && state == .moved { + offset = 0 + } } - - fileprivate var offset: CGFloat = 0 { - didSet { - if offset < 0 && state == .moved { - offset = 0 - } - } - } - - // MARK: - Life cycle - - override open func viewDidLoad() { - super.viewDidLoad() - - addCropable(to: cropContainerView) - cropView.delegate = delegate - } - - open override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - - view.layoutIfNeeded() - updateContent() - } - - open override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if !addedRecognizers { - addedRecognizers = true - addGestureRecognizers() - } + } + + // MARK: - Life cycle + + override open func viewDidLoad() { + super.viewDidLoad() + + addCropable(to: cropContainerView) + cropView.delegate = delegate + } + + open override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + view.layoutIfNeeded() + updateContent() + } + + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if !addedRecognizers { + addedRecognizers = true + addGestureRecognizers() } + } } // MARK: - IBActions extension ATHImagePickerPreviewViewController { - @IBAction func didRecognizeTap(_ rec: UITapGestureRecognizer) { - if state == .folded { - restore(view: holder.floatingView, to: .unfolded, animated: true) - } + @IBAction func didRecognizeTap(_ rec: UITapGestureRecognizer) { + if state == .folded { + restore(view: holder.floatingView, to: .unfolded, animated: true) + } + } + + @IBAction func didRecognizeMainPan(_ rec: UIPanGestureRecognizer) { + guard !holder.isTracking else { return } + + let location = rec.location(in: navigationController?.view) + if holder.floatingView.frame.contains(location) { + holder.isScrollEnabled = false + } else { + holder.isScrollEnabled = true } - @IBAction func didRecognizeMainPan(_ rec: UIPanGestureRecognizer) { - guard !holder.isTracking else { return } - - let location = rec.location(in: navigationController?.view) - if holder.floatingView.frame.contains(location) { - holder.isScrollEnabled = false - } else { - holder.isScrollEnabled = true - } - - if rec.state == .ended || rec.state == .cancelled { - holder.isScrollEnabled = true - } - - guard !isZooming else { return } - - if state == .unfolded { - allowPanOutside = false - } - - receivePanGesture(recognizer: rec, with: holder.floatingView) - - updatePhotoCollectionViewScrolling() + if rec.state == .ended || rec.state == .cancelled { + holder.isScrollEnabled = true } - @IBAction func didRecognizeCheckPan(_ rec: UIPanGestureRecognizer) { - guard !isZooming && !holder.isTracking else { return } - allowPanOutside = true + guard !isZooming else { return } + + if state == .unfolded { + allowPanOutside = false } + + receivePanGesture(recognizer: rec, with: holder.floatingView) + + updatePhotoCollectionViewScrolling() + } + + @IBAction func didRecognizeCheckPan(_ rec: UIPanGestureRecognizer) { + guard !isZooming && !holder.isTracking else { return } + allowPanOutside = true + } } // MARK: - Utils extension ATHImagePickerPreviewViewController { - fileprivate func addGestureRecognizers() { - let pan = UIPanGestureRecognizer(target: self, action: #selector(ATHImagePickerPreviewViewController.didRecognizeMainPan(_:))) - parent?.view.addGestureRecognizer(pan) - pan.delegate = self - - let checkPan = UIPanGestureRecognizer(target: self, action: #selector(ATHImagePickerPreviewViewController.didRecognizeCheckPan(_:))) - holder.floatingView.addGestureRecognizer(checkPan) - checkPan.delegate = self - - let tap = UITapGestureRecognizer(target: self, action: #selector(ATHImagePickerPreviewViewController.didRecognizeTap(_:))) - holder.floatingView.addGestureRecognizer(tap) + fileprivate func addGestureRecognizers() { + let pan = UIPanGestureRecognizer(target: self, action: #selector(ATHImagePickerPreviewViewController.didRecognizeMainPan(_:))) + parent?.view.addGestureRecognizer(pan) + pan.delegate = self + + let checkPan = UIPanGestureRecognizer(target: self, action: #selector(ATHImagePickerPreviewViewController.didRecognizeCheckPan(_:))) + holder.floatingView.addGestureRecognizer(checkPan) + checkPan.delegate = self + + let tap = UITapGestureRecognizer(target: self, action: #selector(ATHImagePickerPreviewViewController.didRecognizeTap(_:))) + holder.floatingView.addGestureRecognizer(tap) + } + + fileprivate func updatePhotoCollectionViewScrolling() { + if state == .moved { + holder?.offset.y = offset + } else { + offset = holder.offset.y } - - fileprivate func updatePhotoCollectionViewScrolling() { - if state == .moved { - holder?.offset.y = offset - } else { - offset = holder.offset.y - } - } - - fileprivate func updateCropViewScrolling() { - if state == .moved { - delegate.isEnabled = false - } else { - delegate.isEnabled = true - } + } + + fileprivate func updateCropViewScrolling() { + if state == .moved { + delegate.isEnabled = false + } else { + delegate.isEnabled = true } + } } // MARK: - FloatingViewLayout extension ATHImagePickerPreviewViewController { - public func prepareForMovement() { - updateCropViewScrolling() - } - - public func didEndMoving() { - updateCropViewScrolling() - } + public func prepareForMovement() { + updateCropViewScrolling() + } + + public func didEndMoving() { + updateCropViewScrolling() + } } // MARK: - Cropable extension ATHImagePickerPreviewViewController { - public func willZoom() { - isZooming = true - } - - public func willEndZooming() { - isZooming = false - } + public func willZoom() { + isZooming = true + } + + public func willEndZooming() { + isZooming = false + } } // MARK: - UIGestureRecognizerDelegate extension ATHImagePickerPreviewViewController: UIGestureRecognizerDelegate { - open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - return true - } - - open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - return true - } + open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } + + open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } } diff --git a/Source/ATHImagePickerSelectionViewController.swift b/Source/ATHImagePickerSelectionViewController.swift index 1731782..0473b05 100644 --- a/Source/ATHImagePickerSelectionViewController.swift +++ b/Source/ATHImagePickerSelectionViewController.swift @@ -11,134 +11,134 @@ import Material import ImagePickerKit open class ATHImagePickerSelectionViewController: UIViewController, SelectionController, StatusBarUpdatable { - - typealias Config = ATHImagePickerStatusBarConfig - - // MARK: - Outlets - - @IBOutlet weak var topConstraint: NSLayoutConstraint! - @IBOutlet weak var topView: UIView! - - // MARK: - Static properties - - open static let identifier = "ATHImagePickerSelectionViewController" - - // MARK: - Properties - - open override var prefersStatusBarHidden: Bool { - return isStatusBarHidden - } - - open override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { - return statusBarAnimation - } - - public weak var previewController: PreviewController? - public weak var assetsController: AssetsController? - - public var constraint: NSLayoutConstraint { - return topConstraint - } - - public var floatingView: UIView { - return topView - } - - public var offset: CGPoint = .zero { - didSet { - assetsController?.offset = offset - } - } - - public var isScrollEnabled: Bool = true { - didSet { - pageTabBarController?.scrollView?.isScrollEnabled = isScrollEnabled - } - } - - public var isTracking: Bool { - return pageTabBarController?.isTracking ?? false - } - - internal weak var commiterDelegate: ATHImagePickerCommiterDelegate? { - didSet { - setupConfig() - } - } - - fileprivate let handler = ATHNavigationBarHandler() - - fileprivate var config: ATHImagePickerPageConfig! { - didSet { - handler.setupItem(navigationItem, config: config, ignoreRight: false, leftHandler: { [weak self] in - self?.commiterDelegate?.commit(item: ATHImagePickerItem(image: nil)) - }) { [weak self] in - self?.commiterDelegate?.commit(item: ATHImagePickerItem(image: self?.floatingView.snapshot())) - } - } - } - - fileprivate var isStatusBarHidden: Bool = false { - didSet { - updateStatusBar(with: config.statusBarConfig) - } - } - - fileprivate var statusBarAnimation: UIStatusBarAnimation = .none { - didSet { - updateStatusBar(with: config.statusBarConfig) - } - } - - // MARK: - Life cycle - - override open func viewDidLoad() { - super.viewDidLoad() - } - - open override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - pageTabBarItem.titleColor = config.titleColor - } - - open override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - pageTabBarItem.titleColor = config.titleInactiveColor - } - - open override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if var embeddedViewController = segue.destination as? EmbededController { - embeddedViewController.holder = self - - if let previewController = embeddedViewController as? PreviewController { - self.previewController = previewController - } else if let assetsController = embeddedViewController as? AssetsController { - self.assetsController = assetsController - } - } - } - - // MARK: - Setup utils - - fileprivate func setupConfig() { - guard let config = commiterDelegate?.commit(configFor: .library) else { - return - } - - self.config = config - - title = config.title - pageTabBarItem.title = config.title - pageTabBarItem.titleColor = view.window != nil ? config.titleColor : config.titleInactiveColor - pageTabBarItem.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: UIFontWeightMedium) - - isStatusBarHidden = config.statusBarConfig.isStatusBarHidden - statusBarAnimation = config.statusBarConfig.statusBarAnimation - } - - // MARK: - SelectionController - - public func commit(error: ATHImagePickerError) { - commiterDelegate?.commit(error: error) - } + + typealias Config = ATHImagePickerStatusBarConfig + + // MARK: - Outlets + + @IBOutlet weak var topConstraint: NSLayoutConstraint! + @IBOutlet weak var topView: UIView! + + // MARK: - Static properties + + open static let identifier = "ATHImagePickerSelectionViewController" + + // MARK: - Properties + + open override var prefersStatusBarHidden: Bool { + return isStatusBarHidden + } + + open override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { + return statusBarAnimation + } + + public weak var previewController: PreviewController? + public weak var assetsController: AssetsController? + + public var constraint: NSLayoutConstraint { + return topConstraint + } + + public var floatingView: UIView { + return topView + } + + public var offset: CGPoint = .zero { + didSet { + assetsController?.offset = offset + } + } + + public var isScrollEnabled: Bool = true { + didSet { + pageTabBarController?.scrollView?.isScrollEnabled = isScrollEnabled + } + } + + public var isTracking: Bool { + return pageTabBarController?.isTracking ?? false + } + + internal weak var commiterDelegate: ATHImagePickerCommiterDelegate? { + didSet { + setupConfig() + } + } + + fileprivate let handler = ATHNavigationBarHandler() + + fileprivate var config: ATHImagePickerPageConfig! { + didSet { + handler.setupItem(navigationItem, config: config, ignoreRight: false, leftHandler: { [weak self] in + self?.commiterDelegate?.commit(item: ATHImagePickerItem(image: nil)) + }) { [weak self] in + self?.commiterDelegate?.commit(item: ATHImagePickerItem(image: self?.floatingView.snapshot())) + } + } + } + + fileprivate var isStatusBarHidden: Bool = false { + didSet { + updateStatusBar(with: config.statusBarConfig) + } + } + + fileprivate var statusBarAnimation: UIStatusBarAnimation = .none { + didSet { + updateStatusBar(with: config.statusBarConfig) + } + } + + // MARK: - Life cycle + + override open func viewDidLoad() { + super.viewDidLoad() + } + + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + pageTabBarItem.titleColor = config.titleColor + } + + open override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + pageTabBarItem.titleColor = config.titleInactiveColor + } + + open override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if var embeddedViewController = segue.destination as? EmbededController { + embeddedViewController.holder = self + + if let previewController = embeddedViewController as? PreviewController { + self.previewController = previewController + } else if let assetsController = embeddedViewController as? AssetsController { + self.assetsController = assetsController + } + } + } + + // MARK: - Setup utils + + fileprivate func setupConfig() { + guard let config = commiterDelegate?.commit(configFor: .library) else { + return + } + + self.config = config + + title = config.title + pageTabBarItem.title = config.title + pageTabBarItem.titleColor = view.window != nil ? config.titleColor : config.titleInactiveColor + pageTabBarItem.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: UIFontWeightMedium) + + isStatusBarHidden = config.statusBarConfig.isStatusBarHidden + statusBarAnimation = config.statusBarConfig.statusBarAnimation + } + + // MARK: - SelectionController + + public func commit(error: ATHImagePickerError) { + commiterDelegate?.commit(error: error) + } } diff --git a/Source/ATHImagePickerTabBarController.swift b/Source/ATHImagePickerTabBarController.swift index 9a7cb7d..3a1736a 100644 --- a/Source/ATHImagePickerTabBarController.swift +++ b/Source/ATHImagePickerTabBarController.swift @@ -10,190 +10,192 @@ import UIKit import Material extension PageTabBarController { - open var isTracking: Bool { - return scrollView?.contentOffset.x != scrollView?.frame.width - } + open var isTracking: Bool { + return scrollView?.contentOffset.x != scrollView?.frame.width + } } open class ATHImagePickerTabBarController: PageTabBarController, StatusBarUpdatable { - - typealias Config = ATHImagePickerStatusBarConfig - - // MARK: - Properties - - open override var prefersStatusBarHidden: Bool { - return isStatusBarHidden - } - - open override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { - return statusBarAnimation - } - - internal weak var commiterDelegate: ATHImagePickerCommiterDelegate? { - didSet { - setupConfig() - } - } - - fileprivate let handler = ATHNavigationBarHandler() - fileprivate var config: ATHImagePickerPageConfig! { - didSet { - title = config.title - - handler.setupItem(navigationItem, config: config, ignoreRight: false, leftHandler: { [weak self] in - self?.commiterDelegate?.commit(item: ATHImagePickerItem(image: nil)) - }) { [weak self] in - guard let image = self?.viewControllers.filter({ $0 is SelectionController}).map({ $0 as! SelectionController }).first?.floatingView.snapshot() else { - return - } - - self?.commiterDelegate?.commit(item: ATHImagePickerItem(image: image)) - } - } - } - - fileprivate var isStatusBarHidden: Bool = false { - didSet { - updateStatusBar(with: config.statusBarConfig) - } - } - - fileprivate var statusBarAnimation: UIStatusBarAnimation = .none { - didSet { - updateStatusBar(with: config.statusBarConfig) - } - } - - fileprivate var changed = false - fileprivate var oldIndex: Int = 0 - fileprivate var currentIndex: Int = 0 { - didSet { - oldIndex = oldValue + + typealias Config = ATHImagePickerStatusBarConfig + + // MARK: - Properties + + open override var prefersStatusBarHidden: Bool { + return isStatusBarHidden + } + + open override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { + return statusBarAnimation + } + + internal weak var commiterDelegate: ATHImagePickerCommiterDelegate? { + didSet { + setupConfig() + } + } + + fileprivate let handler = ATHNavigationBarHandler() + fileprivate var config: ATHImagePickerPageConfig! { + didSet { + title = config.title + + handler.setupItem(navigationItem, config: config, ignoreRight: false, leftHandler: { [weak self] in + self?.commiterDelegate?.commit(item: ATHImagePickerItem(image: nil)) + }) { [weak self] in + guard let image = self?.viewControllers.filter({ $0 is SelectionController}).map({ $0 as! SelectionController }).first?.floatingView.snapshot() else { + return } - } - - fileprivate var countOfPages: Int { - return viewControllers.count - } - - // MARK: - Life cycle - - override open func viewDidLoad() { - super.viewDidLoad() - } - - override open func prepare() { - super.prepare() - delegate = self - preparePageTabBar() - } - - open override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - } - - // MARK: - Setup utils - - fileprivate func setupConfig(for sourceType: ATHImagePickerSourceType = []) { - guard let config = commiterDelegate?.commit(configFor: sourceType) else { - return - } - - self.config = config - - title = config.title - pageTabBarItem.title = config.title - pageTabBarItem.titleColor = config.titleColor - pageTabBarItem.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: UIFontWeightMedium) - - isStatusBarHidden = config.statusBarConfig.isStatusBarHidden - statusBarAnimation = config.statusBarConfig.statusBarAnimation - } + self?.commiterDelegate?.commit(item: ATHImagePickerItem(image: image)) + } + } + } + + fileprivate var isStatusBarHidden: Bool = false { + didSet { + updateStatusBar(with: config.statusBarConfig) + } + } + + fileprivate var statusBarAnimation: UIStatusBarAnimation = .none { + didSet { + updateStatusBar(with: config.statusBarConfig) + } + } + + fileprivate var changed = false + fileprivate var oldIndex: Int = 0 + fileprivate var currentIndex: Int = 0 { + didSet { + oldIndex = oldValue + } + } + + fileprivate var countOfPages: Int { + return viewControllers.count + } + + // MARK: - Life cycle + + override open func viewDidLoad() { + super.viewDidLoad() + } + + override open func prepare() { + super.prepare() + + delegate = self + preparePageTabBar() + } + + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } + + // MARK: - Setup utils + + fileprivate func setupConfig(for sourceType: ATHImagePickerSourceType = []) { + guard let config = commiterDelegate?.commit(configFor: sourceType) else { + return + } + + self.config = config + + title = config.title + pageTabBarItem.title = config.title + pageTabBarItem.titleColor = config.titleColor + pageTabBarItem.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: UIFontWeightMedium) + + isStatusBarHidden = config.statusBarConfig.isStatusBarHidden + statusBarAnimation = config.statusBarConfig.statusBarAnimation + } } // MARK: - UIPageViewController management extension ATHImagePickerTabBarController { - public func presentationIndex(for pageViewController: UIPageViewController) -> Int { - return currentIndex - } + public func presentationIndex(for pageViewController: UIPageViewController) -> Int { + return currentIndex + } } // MARK: - UIScrollViewDelegate extension ATHImagePickerTabBarController { - open override func scrollViewDidScroll(_ scrollView: UIScrollView) { - super.scrollViewDidScroll(scrollView) - - var pageValue = Int((scrollView.contentOffset.x - scrollView.bounds.width) / scrollView.bounds.width) - if pageValue != 0 { - changed = true - if pageValue == -1 { - pageValue = 0 - } - } - - if changed { - currentIndex = pageValue - changed = false - } - - keepInBounds(scrollView: scrollView) - } - - public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { - keepInBounds(scrollView: scrollView) - updateCurrentViewController() - } - - public func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { - keepInBounds(scrollView: scrollView) - } + open override func scrollViewDidScroll(_ scrollView: UIScrollView) { + super.scrollViewDidScroll(scrollView) + + var pageValue = Int((scrollView.contentOffset.x - scrollView.bounds.width) / scrollView.bounds.width) + if pageValue != 0 { + changed = true + if pageValue == -1 { + pageValue = 0 + } + } + + if changed { + currentIndex = pageValue + changed = false + } + + keepInBounds(scrollView: scrollView) + } + + public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + keepInBounds(scrollView: scrollView) + updateCurrentViewController() + } + + public func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { + keepInBounds(scrollView: scrollView) + } } // MAKR: - Utils extension ATHImagePickerTabBarController { - fileprivate func preparePageTabBar() { - pageTabBar.lineColor = .clear - } - - fileprivate func keepInBounds(scrollView: UIScrollView) { - let minOffset = scrollView.bounds.width - CGFloat(currentIndex) * scrollView.bounds.width - let maxOffset = CGFloat(countOfPages - currentIndex) * scrollView.bounds.width - - let bounds = scrollView.bounds - if scrollView.contentOffset.x <= minOffset { - scrollView.contentOffset.x = minOffset - } else if scrollView.contentOffset.x >= maxOffset { - scrollView.contentOffset.x = maxOffset - } - - scrollView.bounds = bounds - - changed = false - } - - fileprivate func updateCurrentViewController() { - let direction: UIPageViewControllerNavigationDirection = oldIndex > currentIndex ? .reverse : .forward - pageViewController?.setViewControllers([viewControllers[currentIndex]], direction: direction, animated: false, completion: nil) - } + fileprivate func preparePageTabBar() { + pageTabBar.lineColor = .clear + } + + fileprivate func keepInBounds(scrollView: UIScrollView) { + let minOffset = scrollView.bounds.width - CGFloat(currentIndex) * scrollView.bounds.width + let maxOffset = CGFloat(countOfPages - currentIndex) * scrollView.bounds.width + + let bounds = scrollView.bounds + if scrollView.contentOffset.x <= minOffset { + scrollView.contentOffset.x = minOffset + } else if scrollView.contentOffset.x >= maxOffset { + scrollView.contentOffset.x = maxOffset + } + + scrollView.bounds = bounds + + changed = false + } + + fileprivate func updateCurrentViewController() { + let direction: UIPageViewControllerNavigationDirection = oldIndex > currentIndex ? .reverse : .forward + pageViewController?.setViewControllers([viewControllers[currentIndex]], direction: direction, animated: false, completion: nil) + } } // MARK: - PageTabBarControllerDelegate extension ATHImagePickerTabBarController: PageTabBarControllerDelegate { - public func pageTabBarController(pageTabBarController: PageTabBarController, didTransitionTo viewController: UIViewController) { - switch viewController { - case is ATHImagePickerSelectionViewController: - setupConfig(for: .library) - - case is ATHImagePickerCaptureViewController: - setupConfig(for: .camera) - - default: - () - } - } + public func pageTabBarController(pageTabBarController: PageTabBarController, didTransitionTo viewController: UIViewController) { + switch viewController { + case is ATHImagePickerSelectionViewController: + setupConfig(for: .library) + navigationItem.rightBarButtonItem?.isEnabled = true + + case is ATHImagePickerCaptureViewController: + setupConfig(for: .camera) + navigationItem.rightBarButtonItem?.isEnabled = false + + default: + () + } + } } diff --git a/Source/ATHNavigationBarHandler.swift b/Source/ATHNavigationBarHandler.swift index 0a37ea6..fea51e5 100644 --- a/Source/ATHNavigationBarHandler.swift +++ b/Source/ATHNavigationBarHandler.swift @@ -9,41 +9,41 @@ import UIKit internal class ATHNavigationBarHandler { - internal var leftHandler: ((Void) -> Void)? - internal var rightHandler: ((Void) -> Void)? + internal var leftHandler: ((Void) -> Void)? + internal var rightHandler: ((Void) -> Void)? + + internal func setupItem(_ item: UINavigationItem, config: ATHImagePickerPageConfig, ignoreRight: Bool = false, leftHandler: ((Void) -> Void)?, rightHandler: ((Void) -> Void)?) { + self.leftHandler = leftHandler + self.rightHandler = rightHandler - internal func setupItem(_ item: UINavigationItem, config: ATHImagePickerPageConfig, ignoreRight: Bool = false, leftHandler: ((Void) -> Void)?, rightHandler: ((Void) -> Void)?) { - self.leftHandler = leftHandler - self.rightHandler = rightHandler - - if !ignoreRight { - let rightItem: UIBarButtonItem - if let image = config.rightButtonImage { - rightItem = UIBarButtonItem(image: image, style: .done, target: self, action: #selector(ATHNavigationBarHandler.didPressRightButton(_:))) - } else { - rightItem = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(ATHNavigationBarHandler.didPressRightButton(_:))) - } - - item.rightBarButtonItem = rightItem - item.rightBarButtonItem?.tintColor = config.rightButtonColor - } - - let leftItem: UIBarButtonItem - if let image = config.leftButtonImage { - leftItem = UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(ATHNavigationBarHandler.didPressLeftButton(_:))) - } else { - leftItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(ATHNavigationBarHandler.didPressLeftButton(_:))) - } - - item.leftBarButtonItem = leftItem - item.leftBarButtonItem?.tintColor = config.leftButtonColor + if !ignoreRight { + let rightItem: UIBarButtonItem + if let image = config.rightButtonImage { + rightItem = UIBarButtonItem(image: image, style: .done, target: self, action: #selector(ATHNavigationBarHandler.didPressRightButton(_:))) + } else { + rightItem = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(ATHNavigationBarHandler.didPressRightButton(_:))) + } + + item.rightBarButtonItem = rightItem + item.rightBarButtonItem?.tintColor = config.rightButtonColor } - @IBAction internal func didPressLeftButton(_ sender: Any) { - leftHandler?() + let leftItem: UIBarButtonItem + if let image = config.leftButtonImage { + leftItem = UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(ATHNavigationBarHandler.didPressLeftButton(_:))) + } else { + leftItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(ATHNavigationBarHandler.didPressLeftButton(_:))) } - @IBAction internal func didPressRightButton(_ sender: Any) { - rightHandler?() - } + item.leftBarButtonItem = leftItem + item.leftBarButtonItem?.tintColor = config.leftButtonColor + } + + @IBAction internal func didPressLeftButton(_ sender: Any) { + leftHandler?() + } + + @IBAction internal func didPressRightButton(_ sender: Any) { + rightHandler?() + } } diff --git a/Source/ATHPhotoCell.swift b/Source/ATHPhotoCell.swift index 2599ce3..8287a5a 100644 --- a/Source/ATHPhotoCell.swift +++ b/Source/ATHPhotoCell.swift @@ -9,41 +9,41 @@ import UIKit internal class ATHPhotoCell: UICollectionViewCell { - // MARK: - Outlets - @IBOutlet weak var photoImageView: UIImageView! + // MARK: - Outlets + @IBOutlet weak var photoImageView: UIImageView! + + // MARK: - Properties + static let identifier = "ATHPhotoCell" + + let overlayView = UIView() + + override var isSelected: Bool { + didSet { + if isSelected { + overlayView.alpha = 0.6 + } else { + overlayView.alpha = 0 + } + } + } + + // MARK: - Life cycle + override func awakeFromNib() { + super.awakeFromNib() - // MARK: - Properties - static let identifier = "ATHPhotoCell" + overlayView.translatesAutoresizingMaskIntoConstraints = false + overlayView.backgroundColor = UIColor.black + overlayView.alpha = 0 - let overlayView = UIView() + addSubview(overlayView) - override var isSelected: Bool { - didSet { - if isSelected { - overlayView.alpha = 0.6 - } else { - overlayView.alpha = 0 - } - } - } + let anchors = [ + overlayView.topAnchor.constraint(equalTo: topAnchor), + overlayView.bottomAnchor.constraint(equalTo: bottomAnchor), + overlayView.leadingAnchor.constraint(equalTo: leadingAnchor), + overlayView.trailingAnchor.constraint(equalTo: trailingAnchor) + ].flatMap { $0 } - // MARK: - Life cycle - override func awakeFromNib() { - super.awakeFromNib() - - overlayView.translatesAutoresizingMaskIntoConstraints = false - overlayView.backgroundColor = UIColor.black - overlayView.alpha = 0 - - addSubview(overlayView) - - let anchors = [ - overlayView.topAnchor.constraint(equalTo: topAnchor), - overlayView.bottomAnchor.constraint(equalTo: bottomAnchor), - overlayView.leadingAnchor.constraint(equalTo: leadingAnchor), - overlayView.trailingAnchor.constraint(equalTo: trailingAnchor) - ].flatMap { $0 } - - NSLayoutConstraint.activate(anchors) - } + NSLayoutConstraint.activate(anchors) + } }