diff --git a/Demo/DemoListViewController.swift b/Demo/DemoListViewController.swift index f8f05c8..8e3e17f 100644 --- a/Demo/DemoListViewController.swift +++ b/Demo/DemoListViewController.swift @@ -30,6 +30,7 @@ class DemoListViewController: UITableViewController, NohanaImagePickerController Cell(title: "No toolbar", selector: #selector(DemoListViewController.showNoToolbarPicker)), Cell(title: "Disable to pick assets", selector: #selector(DemoListViewController.showDisableToPickAssetsPicker)), Cell(title: "Custom UI", selector: #selector(DemoListViewController.showCustomUIPicker)), + Cell(title: "Selectable Album Date Section", selector: #selector(DemoListViewController.showSelectableDateSectionPicker)), ] override func viewDidAppear(_ animated: Bool) { @@ -136,6 +137,13 @@ class DemoListViewController: UITableViewController, NohanaImagePickerController present(picker, animated: true, completion: nil) } + @objc func showSelectableDateSectionPicker() { + let picker = NohanaImagePickerController() + picker.canPickDateSection = true + picker.delegate = self + present(picker, animated: true, completion: nil) + } + // MARK: - NohanaImagePickerControllerDelegate func nohanaImagePickerDidCancel(_ picker: NohanaImagePickerController) { @@ -169,6 +177,10 @@ class DemoListViewController: UITableViewController, NohanaImagePickerController func nohanaImagePicker(_ picker: NohanaImagePickerController, didSelectPhotoKitAsset asset: PHAsset) { print("🐷\(#function)\n\tasset = \(asset)\n\t") } + + func nohanaImagePicker(_ picker: NohanaImagePickerController, didSelectAssetDateSectionAssets assets: [PHAsset], date: Date?) { + print("🐷\(#function)\n\tasset = \(assets)\n\tDate = \(String(describing: date))") + } func nohanaImagePicker(_ picker: NohanaImagePickerController, didSelectPhotoKitAssetList assetList: PHAssetCollection) { print("🐷\(#function)\n\t\tassetList = \(assetList)\n\t") diff --git a/NohanaImagePicker.xcodeproj/project.pbxproj b/NohanaImagePicker.xcodeproj/project.pbxproj index 5ec541b..b91be97 100644 --- a/NohanaImagePicker.xcodeproj/project.pbxproj +++ b/NohanaImagePicker.xcodeproj/project.pbxproj @@ -8,7 +8,12 @@ /* Begin PBXBuildFile section */ 3569CAA91EC1918E000C41C0 /* NohanaImagePicker.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3569CAA61EC1918E000C41C0 /* NohanaImagePicker.xcassets */; }; + F117F732273B6A2600E11BC7 /* AssetDateSectionCreater.swift in Sources */ = {isa = PBXBuildFile; fileRef = F117F731273B6A2600E11BC7 /* AssetDateSectionCreater.swift */; }; F181095026A5361A001C2BDE /* MomentDetailListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F181094F26A5361A001C2BDE /* MomentDetailListViewController.swift */; }; + F1A26CCD2738DE6A00433E9F /* AssetListSelectableDateSection.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F1A26CCC2738DE6A00433E9F /* AssetListSelectableDateSection.storyboard */; }; + F1A26CCF2738E7E400433E9F /* AssetListSelectableDateSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A26CCE2738E7E400433E9F /* AssetListSelectableDateSectionController.swift */; }; + F1A26CD12738E88700433E9F /* AssetDateSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A26CD02738E88700433E9F /* AssetDateSection.swift */; }; + F1A26CD32739194300433E9F /* AssetDateSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A26CD22739194300433E9F /* AssetDateSectionHeaderView.swift */; }; F1E5DE7D26A57F0B004B9EDE /* DetailListViewControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E5DE7C26A57F0B004B9EDE /* DetailListViewControllerProtocol.swift */; }; F1E5DE8026A58346004B9EDE /* MomentInfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E5DE7F26A58346004B9EDE /* MomentInfoSection.swift */; }; F1E5DE8226A58386004B9EDE /* MomentInfoSectionCreater.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E5DE8126A58386004B9EDE /* MomentInfoSectionCreater.swift */; }; @@ -77,7 +82,12 @@ 23D1CD93207CEB1200F8115E /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/NohanaImagePicker.strings; sourceTree = ""; }; 3569CAA61EC1918E000C41C0 /* NohanaImagePicker.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = NohanaImagePicker.xcassets; sourceTree = ""; }; 3590F1F51EC1A79400F32E06 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/NohanaImagePicker.strings; sourceTree = ""; }; + F117F731273B6A2600E11BC7 /* AssetDateSectionCreater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetDateSectionCreater.swift; sourceTree = ""; }; F181094F26A5361A001C2BDE /* MomentDetailListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MomentDetailListViewController.swift; sourceTree = ""; }; + F1A26CCC2738DE6A00433E9F /* AssetListSelectableDateSection.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = AssetListSelectableDateSection.storyboard; sourceTree = ""; }; + F1A26CCE2738E7E400433E9F /* AssetListSelectableDateSectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetListSelectableDateSectionController.swift; sourceTree = ""; }; + F1A26CD02738E88700433E9F /* AssetDateSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetDateSection.swift; sourceTree = ""; }; + F1A26CD22739194300433E9F /* AssetDateSectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetDateSectionHeaderView.swift; sourceTree = ""; }; F1E5DE7C26A57F0B004B9EDE /* DetailListViewControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailListViewControllerProtocol.swift; sourceTree = ""; }; F1E5DE7F26A58346004B9EDE /* MomentInfoSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MomentInfoSection.swift; sourceTree = ""; }; F1E5DE8126A58386004B9EDE /* MomentInfoSectionCreater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MomentInfoSectionCreater.swift; sourceTree = ""; }; @@ -153,6 +163,8 @@ children = ( F1E5DE7F26A58346004B9EDE /* MomentInfoSection.swift */, F1E5DE8126A58386004B9EDE /* MomentInfoSectionCreater.swift */, + F1A26CD02738E88700433E9F /* AssetDateSection.swift */, + F117F731273B6A2600E11BC7 /* AssetDateSectionCreater.swift */, ); name = Models; sourceTree = ""; @@ -180,6 +192,7 @@ F25C69871CA23A0A005935D6 /* MomentCell.swift */, F2FE1F761C901D9400FDBE7B /* MomentSectionHeaderView.swift */, F25C69911CA28728005935D6 /* AlbumListEmptyIndicator.swift */, + F1A26CD22739194300433E9F /* AssetDateSectionHeaderView.swift */, ); name = Views; sourceTree = ""; @@ -188,6 +201,7 @@ isa = PBXGroup; children = ( F23554251C69D19C00796DCA /* NohanaImagePicker.storyboard */, + F1A26CCC2738DE6A00433E9F /* AssetListSelectableDateSection.storyboard */, F27029CD1C71C43A001647AB /* NohanaImagePicker.strings */, F237249A1C6DCF96005D1E8A /* NohanaImagePicker.xcassets */, ); @@ -272,6 +286,7 @@ F2DF3B2A1C6CC4DB00C1C0E4 /* AssetDetailListViewController.swift */, F181094F26A5361A001C2BDE /* MomentDetailListViewController.swift */, F1E5DE7C26A57F0B004B9EDE /* DetailListViewControllerProtocol.swift */, + F1A26CCE2738E7E400433E9F /* AssetListSelectableDateSectionController.swift */, ); name = ViewControllers; sourceTree = ""; @@ -388,6 +403,7 @@ F23554261C69D19C00796DCA /* NohanaImagePicker.storyboard in Resources */, F27029CB1C71C43A001647AB /* NohanaImagePicker.strings in Resources */, F237249B1C6DCF96005D1E8A /* NohanaImagePicker.xcassets in Resources */, + F1A26CCD2738DE6A00433E9F /* AssetListSelectableDateSection.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -416,6 +432,7 @@ F25C10C21C8ED9BF007453C3 /* MomentViewController.swift in Sources */, F25C69881CA23A0A005935D6 /* MomentCell.swift in Sources */, F2FE1F781C901D9400FDBE7B /* MomentSectionHeaderView.swift in Sources */, + F1A26CD12738E88700433E9F /* AssetDateSection.swift in Sources */, F23554281C69D5DB00796DCA /* NohanaImagePickerController.swift in Sources */, F1E5DE8026A58346004B9EDE /* MomentInfoSection.swift in Sources */, F181095026A5361A001C2BDE /* MomentDetailListViewController.swift in Sources */, @@ -423,12 +440,15 @@ F2131F431C79615700797887 /* SwipeInteractionController.swift in Sources */, F2DF3B2D1C6D780100C1C0E4 /* AssetDetailCell.swift in Sources */, F25C69901CA27311005935D6 /* EmptyIndicatable.swift in Sources */, + F117F732273B6A2600E11BC7 /* AssetDateSectionCreater.swift in Sources */, F2DA29771C7749D600B0A8E3 /* NotificationInfo.swift in Sources */, + F1A26CCF2738E7E400433E9F /* AssetListSelectableDateSectionController.swift in Sources */, F1E5DE7D26A57F0B004B9EDE /* DetailListViewControllerProtocol.swift in Sources */, F218D7DE1C6C3A5B001FCED1 /* AlbumCell.swift in Sources */, F26775E11C7046C7002E786C /* ExpandingAnimationController.swift in Sources */, F28F4AC51C6C59A500B7D725 /* PhotoKitAsset.swift in Sources */, F2DF3B151C6C76E500C1C0E4 /* AssetListViewController.swift in Sources */, + F1A26CD32739194300433E9F /* AssetDateSectionHeaderView.swift in Sources */, F26775E51C70574F002E786C /* AnimatableNavigationController.swift in Sources */, F2DF3B2B1C6CC4DB00C1C0E4 /* AssetDetailListViewController.swift in Sources */, F218D7D61C6B3D22001FCED1 /* PhotoKitAlbumList.swift in Sources */, diff --git a/NohanaImagePicker/AlbumListViewController.swift b/NohanaImagePicker/AlbumListViewController.swift index 5b2ac42..e31f9fb 100644 --- a/NohanaImagePicker/AlbumListViewController.swift +++ b/NohanaImagePicker/AlbumListViewController.swift @@ -82,6 +82,11 @@ class AlbumListViewController: UITableViewController, EmptyIndicatable, Activity case .moment: nohanaImagePickerController.delegate?.nohanaImagePickerDidSelectMoment?(nohanaImagePickerController) case .albums: + if nohanaImagePickerController.canPickDateSection { + performSegue(withIdentifier: "toAssetListViewSelectableDateSectionController", sender: nil) + } else { + performSegue(withIdentifier: "toAssetListViewController", sender: nil) + } nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, didSelectPhotoKitAssetList: photoKitAlbumList[indexPath.row].assetList) } } @@ -156,7 +161,7 @@ class AlbumListViewController: UITableViewController, EmptyIndicatable, Activity ) let albumCount = albumList.count if albumCount > 0 { - let lastAsset = albumList[albumCount - 1] + let lastAsset = nohanaImagePickerController?.canPickDateSection ?? false ? albumList[0] : albumList[albumCount - 1] lastAsset.image(targetSize: imageSize, handler: { (imageData) -> Void in DispatchQueue.main.async(execute: { () -> Void in if let imageData = imageData { @@ -184,9 +189,18 @@ class AlbumListViewController: UITableViewController, EmptyIndicatable, Activity let momentViewController = segue.destination as! MomentViewController momentViewController.nohanaImagePickerController = nohanaImagePickerController case .albums: - let assetListViewController = segue.destination as! AssetListViewController - assetListViewController.photoKitAssetList = photoKitAlbumList[tableView.indexPathForSelectedRow!.row] - assetListViewController.nohanaImagePickerController = nohanaImagePickerController + switch segue.identifier { + case "toAssetListViewController": + let assetListViewController = segue.destination as! AssetListViewController + assetListViewController.photoKitAssetList = photoKitAlbumList[tableView.indexPathForSelectedRow!.row] + assetListViewController.nohanaImagePickerController = nohanaImagePickerController + case "toAssetListViewSelectableDateSectionController": + let assetListSelectableDateSectionController = segue.destination as! AssetListSelectableDateSectionController + assetListSelectableDateSectionController.photoKitAssetList = photoKitAlbumList[tableView.indexPathForSelectedRow!.row] + assetListSelectableDateSectionController.nohanaImagePickerController = nohanaImagePickerController + default: + fatalError("unexpected segue identifer") + } } } diff --git a/NohanaImagePicker/AssetCell.swift b/NohanaImagePicker/AssetCell.swift index ca1a299..0410811 100644 --- a/NohanaImagePicker/AssetCell.swift +++ b/NohanaImagePicker/AssetCell.swift @@ -15,12 +15,17 @@ */ import UIKit +protocol AssetCellDelegate: AnyObject { + func didPushPickButton(cell: AssetCell) +} + class AssetCell: UICollectionViewCell { @IBOutlet weak var imageView: UIImageView! @IBOutlet weak var pickButton: UIButton! @IBOutlet weak var overlayView: UIView! + weak var delegate: AssetCellDelegate? weak var nohanaImagePickerController: NohanaImagePickerController? var asset: Asset? @@ -49,6 +54,7 @@ class AssetCell: UICollectionViewCell { } } self.overlayView.isHidden = !pickButton.isSelected + delegate?.didPushPickButton(cell: self) } func update(asset: Asset, nohanaImagePickerController: NohanaImagePickerController) { diff --git a/NohanaImagePicker/AssetDateSection.swift b/NohanaImagePicker/AssetDateSection.swift new file mode 100644 index 0000000..bc582bf --- /dev/null +++ b/NohanaImagePicker/AssetDateSection.swift @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 nohana, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Photos + +struct AssetDateSection { + let creationDate: Date + let assetResult: [PHAsset] +} diff --git a/NohanaImagePicker/AssetDateSectionCreater.swift b/NohanaImagePicker/AssetDateSectionCreater.swift new file mode 100644 index 0000000..0cbf12a --- /dev/null +++ b/NohanaImagePicker/AssetDateSectionCreater.swift @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 nohana, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Photos + +final class AssetDateSectionCreater { + func createSections(assetList: PHAssetCollection, options: PHFetchOptions) -> [AssetDateSection] { + var albumDateSectionList = [AssetDateSection]() + let fetchAssetlist = PHAsset.fetchAssets(in: assetList, options: options) + let allAssets = fetchAssetlist.objects(at: IndexSet(0.. 0 { + if assetsByDate[assetsByDateIndex - 1].0 == calender.dateComponents([.day, .year, .month], from: (asset.creationDate ?? Date(timeIntervalSince1970: 0))) { + assetsByDate[assetsByDateIndex - 1].1.append(asset) + } else { + let value = (calender.dateComponents([.day, .year, .month], from: (asset.creationDate ?? Date(timeIntervalSince1970: 0))), [asset]) + assetsByDate.append(value) + assetsByDateIndex += 1 + } + } else if assetsByDate.count == assetsByDateIndex { + let value = (calender.dateComponents([.day, .year, .month], from: (asset.creationDate ?? Date(timeIntervalSince1970: 0))), [asset]) + assetsByDate.append(value) + assetsByDateIndex += 1 + } + } + albumDateSectionList = assetsByDate.map { AssetDateSection(creationDate: calender.date(from: $0.0) ?? Date(timeIntervalSince1970: 0), assetResult: $0.1) } + + return albumDateSectionList + } +} diff --git a/NohanaImagePicker/AssetDateSectionHeaderView.swift b/NohanaImagePicker/AssetDateSectionHeaderView.swift new file mode 100644 index 0000000..d6d2c08 --- /dev/null +++ b/NohanaImagePicker/AssetDateSectionHeaderView.swift @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2021 nohana, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import UIKit + +protocol AssetDateSectionHeaderViewDelegate: AnyObject { + func didPushPickButton() +} + +class AssetDateSectionHeaderView: UICollectionReusableView { + @IBOutlet weak private var dateLabel: UILabel! + @IBOutlet weak private var pickButton: UIButton! + var date: Date? { + didSet { + if let dete = date { + let formatter = DateFormatter() + formatter.dateStyle = .long + formatter.timeStyle = DateFormatter.Style.none + dateLabel.text = formatter.string(from: dete) + } else { + dateLabel.text = "" + } + } + } + var assets = [Asset]() + weak var nohanaImagePickerController: NohanaImagePickerController? + weak var delegate: AssetDateSectionHeaderViewDelegate? + + @IBAction func didPushPickButton(_ sender: UIButton) { + guard let nohanaImagePickerController = nohanaImagePickerController else { return } + let firstButtonState = sender.isSelected + for asset in assets { + guard nohanaImagePickerController.canPickAsset(asset) else { continue } + if firstButtonState { + _ = nohanaImagePickerController.pickedAssetList.drop(asset: asset) + sender.isSelected = false + } else { + if nohanaImagePickerController.pickedAssetList.isPicked(asset) { + continue + } else if nohanaImagePickerController.pickedAssetList.pick(asset: asset) { + sender.isSelected = true + } else if nohanaImagePickerController.pickedAssetList.count == nohanaImagePickerController.maximumNumberOfSelection { + sender.isSelected = false + break + } + } + } + delegate?.didPushPickButton() + nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, didSelectAssetDateSectionAssets: assets.compactMap { ($0 as? PhotoKitAsset)?.originalAsset }, date: date) + } + + func update(assets: [Asset], indexPath: IndexPath, nohanaImagePickerController: NohanaImagePickerController) { + self.assets = assets + self.nohanaImagePickerController = nohanaImagePickerController + if pickButton.image(for: UIControl.State()) == nil, pickButton.image(for: .selected) == nil { + let droppedImage: UIImage? = nohanaImagePickerController.config.image.droppedLarge ?? UIImage(named: "btn_select_l", in: nohanaImagePickerController.assetBundle, compatibleWith: nil) + let pickedImage: UIImage? = nohanaImagePickerController.config.image.pickedLarge ?? UIImage(named: "btn_selected_l", in: nohanaImagePickerController.assetBundle, compatibleWith: nil) + pickButton.setImage(droppedImage, for: UIControl.State()) + pickButton.setImage(pickedImage, for: .selected) + } + + let canPick = assets.contains(where: { nohanaImagePickerController.canPickAsset($0) == true }) + pickButton.isHidden = !canPick + if !canPick { return } + let canPickAssets = assets.compactMap { nohanaImagePickerController.canPickAsset($0) ? $0 : nil } + let existNotSelected = canPickAssets.contains(where: { nohanaImagePickerController.pickedAssetList.isPicked($0) == false }) + pickButton.isSelected = !existNotSelected + } +} diff --git a/NohanaImagePicker/AssetDetailListViewController.swift b/NohanaImagePicker/AssetDetailListViewController.swift index dfe1b4a..f7f3d8f 100644 --- a/NohanaImagePicker/AssetDetailListViewController.swift +++ b/NohanaImagePicker/AssetDetailListViewController.swift @@ -31,6 +31,11 @@ class AssetDetailListViewController: AssetListViewController, DetailListViewCont override var cellSize: CGSize { return Size.screenRectWithoutAppBar(self).size } + + override func loadView() { + super.loadView() + self.collectionView.backgroundColor = .black + } override func viewDidLoad() { super.viewDidLoad() diff --git a/NohanaImagePicker/AssetListSelectableDateSection.storyboard b/NohanaImagePicker/AssetListSelectableDateSection.storyboard new file mode 100644 index 0000000..d950361 --- /dev/null +++ b/NohanaImagePicker/AssetListSelectableDateSection.storyboard @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NohanaImagePicker/AssetListSelectableDateSectionController.swift b/NohanaImagePicker/AssetListSelectableDateSectionController.swift new file mode 100644 index 0000000..a11096a --- /dev/null +++ b/NohanaImagePicker/AssetListSelectableDateSectionController.swift @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2021 nohana, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation +import Photos + +class AssetListSelectableDateSectionController: UICollectionViewController, UICollectionViewDelegateFlowLayout, ActivityIndicatable { + + weak var nohanaImagePickerController: NohanaImagePickerController? + var photoKitAssetList: PhotoKitAssetList! + var dateSectionList: [AssetDateSection] = [] + + var cellSize: CGSize { + guard let nohanaImagePickerController = nohanaImagePickerController else { + return CGSize.zero + } + var numberOfColumns = nohanaImagePickerController.numberOfColumnsInLandscape + if UIApplication.shared.statusBarOrientation.isPortrait { + numberOfColumns = nohanaImagePickerController.numberOfColumnsInPortrait + } + let cellMargin: CGFloat = 2 + let cellWidth = (view.frame.width - cellMargin * (CGFloat(numberOfColumns) - 1)) / CGFloat(numberOfColumns) + return CGSize(width: cellWidth, height: cellWidth) + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = nohanaImagePickerController?.config.color.background ?? .white + updateTitle() + setUpToolbarItems() + addPickPhotoKitAssetNotificationObservers() + setUpActivityIndicator() + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.dateSectionList = AssetDateSectionCreater().createSections(assetList: self.photoKitAssetList.assetList, options: PhotoKitAssetList.fetchOptions(self.photoKitAssetList.mediaType, ascending: false)) + self.isLoading = false + self.collectionView?.reloadData() + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if let nohanaImagePickerController = nohanaImagePickerController { + setToolbarTitle(nohanaImagePickerController) + } + collectionView?.reloadData() + } + + func updateTitle() { + title = photoKitAssetList.title + } + + func scrollCollectionView(to indexPath: IndexPath) { + let count: Int? = dateSectionList.count + guard count != nil && count! > 0 else { + return + } + DispatchQueue.main.async { + self.collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: false) + } + } + + // MARK: - UICollectionViewDataSource + + override func numberOfSections(in collectionView: UICollectionView) -> Int { + if let activityIndicator = activityIndicator { + updateVisibilityOfActivityIndicator(activityIndicator) + } + + return dateSectionList.count + } + + override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dateSectionList[section].assetResult.count + } + + // MARK: - UICollectionViewDelegate + + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AssetCell", for: indexPath) as? AssetCell, + let nohanaImagePickerController = nohanaImagePickerController else { + fatalError("failed to dequeueReusableCellWithIdentifier(\"AssetCell\")") + } + + let asset = PhotoKitAsset(asset: dateSectionList[indexPath.section].assetResult[indexPath.row]) + cell.tag = indexPath.item + cell.delegate = self + cell.update(asset: asset, nohanaImagePickerController: nohanaImagePickerController) + + let imageSize = CGSize( + width: cellSize.width * UIScreen.main.scale, + height: cellSize.height * UIScreen.main.scale + ) + asset.image(targetSize: imageSize) { (imageData) -> Void in + DispatchQueue.main.async(execute: { () -> Void in + if let imageData = imageData { + if cell.tag == indexPath.item { + cell.imageView.image = imageData.image + } + } + }) + } + return (nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, assetListViewController: self, cell: cell, indexPath: indexPath, photoKitAsset: asset.originalAsset)) ?? cell + } + + override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + switch kind { + case UICollectionView.elementKindSectionHeader: + let album = dateSectionList[indexPath.section] + guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "AssetDateSectionHeader", for: indexPath) as? AssetDateSectionHeaderView, + let nohanaImagePickerController = nohanaImagePickerController else { + fatalError("failed to create AssetDateSectionHeader") + } + header.date = album.creationDate + header.delegate = self + let assets = dateSectionList[indexPath.section].assetResult.map { PhotoKitAsset(asset: $0) } + header.update(assets: assets, indexPath: indexPath, nohanaImagePickerController: nohanaImagePickerController) + return header + default: + fatalError("failed to create AssetDateSectionHeader") + } + } + + // MARK: - UICollectionViewDelegateFlowLayout + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return cellSize + } + + // MARK: - ActivityIndicatable + + var activityIndicator: UIActivityIndicatorView? + var isLoading = true + + func setUpActivityIndicator() { + activityIndicator = UIActivityIndicatorView(style: .gray) + let screenRect = Size.screenRectWithoutAppBar(self) + activityIndicator?.center = CGPoint(x: screenRect.size.width / 2, y: screenRect.size.height / 2) + activityIndicator?.startAnimating() + } + + func isProgressing() -> Bool { + return isLoading + } + + // MARK: - UICollectionViewDelegate + + override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if let nohanaImagePickerController = nohanaImagePickerController { + nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, didSelectPhotoKitAsset: dateSectionList[indexPath.section].assetResult[indexPath.row]) + } + } + + // MARK: - Storyboard + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + guard let selectedIndexPath = collectionView?.indexPathsForSelectedItems?.first, + selectedIndexPath.section < dateSectionList.count else { + return + } + var assetListDetailCurrentRow = 0 + for section in 0..<(selectedIndexPath.section + 1) { + if selectedIndexPath.section == section { + assetListDetailCurrentRow += selectedIndexPath.row + } else { + assetListDetailCurrentRow += dateSectionList[section].assetResult.count + } + } + if assetListDetailCurrentRow >= photoKitAssetList.count { + assetListDetailCurrentRow = photoKitAssetList.count - 1 + } + + let assetListDetailViewController = segue.destination as! AssetDetailListViewController + assetListDetailViewController.photoKitAssetList = photoKitAssetList + assetListDetailViewController.nohanaImagePickerController = nohanaImagePickerController + assetListDetailViewController.currentIndexPath = IndexPath(item: assetListDetailCurrentRow, section: 0) + } + + // MARK: - IBAction + + @IBAction func didPushDone(_ sender: AnyObject) { + if let nohanaImagePickerController = nohanaImagePickerController { + let pickedPhotoKitAssets = nohanaImagePickerController.pickedAssetList.map { ($0 as! PhotoKitAsset).originalAsset } + nohanaImagePickerController.delegate?.nohanaImagePicker(nohanaImagePickerController, didFinishPickingPhotoKitAssets: pickedPhotoKitAssets) + } + } +} + +// MARK: - AssetDateSectionHeaderViewDelegate +extension AssetListSelectableDateSectionController: AssetDateSectionHeaderViewDelegate { + func didPushPickButton() { + collectionView.reloadItems(at: collectionView.indexPathsForVisibleItems) + } +} + +// MARK: - AssetCellDelegate +extension AssetListSelectableDateSectionController: AssetCellDelegate { + func didPushPickButton(cell: AssetCell) { + if let indexPath = collectionView.indexPath(for: cell) { + if #available(iOS 9.0, *) { + let rowResetIndexPath = IndexPath(row: 0, section: indexPath.section) + let header = collectionView.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, at: rowResetIndexPath) as? AssetDateSectionHeaderView + let assets = dateSectionList[indexPath.section].assetResult.map { PhotoKitAsset(asset: $0) } + header?.update(assets: assets, indexPath: indexPath, nohanaImagePickerController: nohanaImagePickerController!) + } else { + UIView.animate(withDuration: 0) { [weak self] in + self?.collectionView.performBatchUpdates({ [weak self] in + let indexSet = IndexSet(integer: indexPath.section) + self?.collectionView.reloadSections(indexSet) + }, completion: nil) + } + } + } + } +} diff --git a/NohanaImagePicker/MomentDetailListViewController.swift b/NohanaImagePicker/MomentDetailListViewController.swift index 722fdd6..31f6788 100644 --- a/NohanaImagePicker/MomentDetailListViewController.swift +++ b/NohanaImagePicker/MomentDetailListViewController.swift @@ -35,6 +35,11 @@ final class MomentDetailListViewController: UICollectionViewController, UICollec weak var nohanaImagePickerController: NohanaImagePickerController? var momentInfoSection: MomentInfoSection? var isFirstAppearance = true + + override func loadView() { + super.loadView() + self.collectionView.backgroundColor = .black + } override func viewDidLoad() { super.viewDidLoad() diff --git a/NohanaImagePicker/NohanaImagePicker.storyboard b/NohanaImagePicker/NohanaImagePicker.storyboard index 986a9e1..ff9f532 100644 --- a/NohanaImagePicker/NohanaImagePicker.storyboard +++ b/NohanaImagePicker/NohanaImagePicker.storyboard @@ -1,9 +1,9 @@ - + - + @@ -49,7 +49,6 @@ - @@ -96,6 +95,10 @@ + + + + @@ -282,8 +285,19 @@ + + + + + + + + + + + @@ -301,7 +315,18 @@ + + + + + + + + + + + @@ -513,6 +538,16 @@ + + + + + + + + + + diff --git a/NohanaImagePicker/NohanaImagePickerController.swift b/NohanaImagePicker/NohanaImagePickerController.swift index ffea723..9826ef5 100644 --- a/NohanaImagePicker/NohanaImagePickerController.swift +++ b/NohanaImagePicker/NohanaImagePickerController.swift @@ -29,12 +29,12 @@ public enum MediaType: Int { @objc optional func nohanaImagePicker(_ picker: NohanaImagePickerController, willDropPhotoKitAsset asset: PHAsset, pickedAssetsCount: Int) -> Bool @objc optional func nohanaImagePicker(_ picker: NohanaImagePickerController, didDropPhotoKitAsset asset: PHAsset, pickedAssetsCount: Int) @objc optional func nohanaImagePicker(_ picker: NohanaImagePickerController, didSelectPhotoKitAsset asset: PHAsset) + @objc optional func nohanaImagePicker(_ picker: NohanaImagePickerController, didSelectAssetDateSectionAssets assets: [PHAsset], date: Date?) @objc optional func nohanaImagePicker(_ picker: NohanaImagePickerController, didSelectPhotoKitAssetList assetList: PHAssetCollection) @objc optional func nohanaImagePickerDidSelectMoment(_ picker: NohanaImagePickerController) -> Void @objc optional func nohanaImagePicker(_ picker: NohanaImagePickerController, assetListViewController: UICollectionViewController, cell: UICollectionViewCell, indexPath: IndexPath, photoKitAsset: PHAsset) -> UICollectionViewCell @objc optional func nohanaImagePicker(_ picker: NohanaImagePickerController, assetDetailListViewController: UICollectionViewController, cell: UICollectionViewCell, indexPath: IndexPath, photoKitAsset: PHAsset) -> UICollectionViewCell @objc optional func nohanaImagePicker(_ picker: NohanaImagePickerController, assetDetailListViewController: UICollectionViewController, didChangeAssetDetailPage indexPath: IndexPath, photoKitAsset: PHAsset) - } open class NohanaImagePickerController: UIViewController { @@ -50,6 +50,7 @@ open class NohanaImagePickerController: UIViewController { return true } open var config: Config = Config() + open var canPickDateSection: Bool = false lazy var assetBundle: Bundle = { let bundle = Bundle(for: type(of: self)) @@ -120,6 +121,7 @@ open class NohanaImagePickerController: UIViewController { assetCollectionSubtypes: assetCollectionSubtypes, mediaType: mediaType, shouldShowEmptyAlbum: shouldShowEmptyAlbum, + ascending: !canPickDateSection, handler: { [weak albumListViewController] in DispatchQueue.main.async(execute: { () -> Void in albumListViewController?.isLoading = false diff --git a/NohanaImagePicker/PhotoKitAlbumList.swift b/NohanaImagePicker/PhotoKitAlbumList.swift index 994ee44..bca1843 100644 --- a/NohanaImagePicker/PhotoKitAlbumList.swift +++ b/NohanaImagePicker/PhotoKitAlbumList.swift @@ -22,14 +22,16 @@ public class PhotoKitAlbumList: ItemList { private let assetCollectionSubtypes: [PHAssetCollectionSubtype] private let mediaType: MediaType private var shouldShowEmptyAlbum: Bool + private let ascending: Bool // MARK: - init - init(assetCollectionTypes: [PHAssetCollectionType], assetCollectionSubtypes: [PHAssetCollectionSubtype], mediaType: MediaType, shouldShowEmptyAlbum: Bool, handler:(() -> Void)?) { + init(assetCollectionTypes: [PHAssetCollectionType], assetCollectionSubtypes: [PHAssetCollectionSubtype], mediaType: MediaType, shouldShowEmptyAlbum: Bool, ascending: Bool, handler:(() -> Void)?) { self.assetCollectionTypes = assetCollectionTypes self.assetCollectionSubtypes = assetCollectionSubtypes self.mediaType = mediaType self.shouldShowEmptyAlbum = shouldShowEmptyAlbum + self.ascending = ascending update { () -> Void in if let handler = handler { handler() @@ -59,7 +61,7 @@ public class PhotoKitAlbumList: ItemList { fetchResult.enumerateObjects({ (album, index, stop) in if self.assetCollectionSubtypes.contains(album.assetCollectionSubtype) || isAssetCollectionSubtypeAny { if self.shouldShowEmptyAlbum || PHAsset.fetchAssets(in: album, options: PhotoKitAssetList.fetchOptions(self.mediaType)).count != 0 { - tmpAlbumList.append(PhotoKitAssetList(album: album, mediaType: self.mediaType)) + tmpAlbumList.append(PhotoKitAssetList(album: album, mediaType: self.mediaType, ascending: self.ascending)) } } }) diff --git a/NohanaImagePicker/PhotoKitAssetList.swift b/NohanaImagePicker/PhotoKitAssetList.swift index d66f59b..d70bf5c 100644 --- a/NohanaImagePicker/PhotoKitAssetList.swift +++ b/NohanaImagePicker/PhotoKitAssetList.swift @@ -18,13 +18,15 @@ import Photos open class PhotoKitAssetList: ItemList { - fileprivate let mediaType: MediaType + public let mediaType: MediaType public let assetList: PHAssetCollection + private let ascending: Bool fileprivate var fetchResult: PHFetchResult! - init(album: PHAssetCollection, mediaType: MediaType) { + init(album: PHAssetCollection, mediaType: MediaType, ascending: Bool) { self.assetList = album self.mediaType = mediaType + self.ascending = ascending update() } @@ -40,11 +42,12 @@ open class PhotoKitAssetList: ItemList { return assetList.startDate } - class func fetchOptions(_ mediaType: MediaType) -> PHFetchOptions { + class func fetchOptions(_ mediaType: MediaType, ascending: Bool = true) -> PHFetchOptions { let options = PHFetchOptions() switch mediaType { case .photo: options.predicate = NSPredicate(format: "mediaType == %ld", PHAssetMediaType.image.rawValue) + options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: ascending)] default: fatalError("not supported .Video and .Any yet") } @@ -52,7 +55,7 @@ open class PhotoKitAssetList: ItemList { } open func update(_ handler: (() -> Void)? = nil) { - fetchResult = PHAsset.fetchAssets(in: assetList, options: PhotoKitAssetList.fetchOptions(mediaType)) + fetchResult = PHAsset.fetchAssets(in: assetList, options: PhotoKitAssetList.fetchOptions(mediaType, ascending: ascending)) if let handler = handler { handler() }