├── .DS_Store ├── .gitignore ├── DXPhotoPicker ├── Classes │ ├── Controllers │ │ ├── DXAlbumTableViewController.swift │ │ ├── DXImageFlowViewController.swift │ │ ├── DXPhotoBrowser.swift │ │ └── DXPhotoPickerController.swift │ ├── Models │ │ └── DXAlbum.swift │ └── Views │ │ ├── DXAlbumCell.swift │ │ ├── DXAssetCell.swift │ │ ├── DXBadgeLabel.swift │ │ ├── DXBrowserCell.swift │ │ ├── DXFullImageButton.swift │ │ ├── DXPromptView.swift │ │ ├── DXSendButton.swift │ │ ├── DXTapDetectingImageView.swift │ │ └── DXUnAuthorizedTipsView.swift ├── Resources │ ├── en.lproj │ │ └── DXPhotoPicker.strings │ ├── imageResources.xcassets │ │ ├── Contents.json │ │ └── images │ │ │ ├── assets_placeholder_picture.imageset │ │ │ ├── Contents.json │ │ │ └── assets_placeholder_picture@2x.png │ │ │ ├── back_highlight.imageset │ │ │ ├── Contents.json │ │ │ └── back_highlight.pdf │ │ │ ├── back_normal.imageset │ │ │ ├── Contents.json │ │ │ └── back_normal.pdf │ │ │ ├── compose_photograph_background.imageset │ │ │ ├── Contents.json │ │ │ └── compose_photograph_background@2x.png │ │ │ ├── image_unAuthorized.imageset │ │ │ ├── Contents.json │ │ │ └── image_unAuthorized.pdf │ │ │ ├── photo_check_default.imageset │ │ │ ├── Contents.json │ │ │ └── photo_check_default.pdf │ │ │ ├── photo_check_selected.imageset │ │ │ ├── Contents.json │ │ │ └── photo_check_selected.pdf │ │ │ ├── photo_full_image_selected.imageset │ │ │ ├── Contents.json │ │ │ └── photo_full_image_selected.pdf │ │ │ ├── photo_full_image_unselected.imageset │ │ │ ├── Contents.json │ │ │ └── photo_full_image_unselected.pdf │ │ │ ├── picker_alert_sigh.imageset │ │ │ ├── Contents.json │ │ │ └── picker_alert_sigh@2x.png │ │ │ └── picker_image_placeholder.imageset │ │ │ ├── Contents.json │ │ │ └── picker_image_placeholder@2x.png │ └── zh-Hans.lproj │ │ └── DXPhotoPicker.strings └── Support │ ├── DXPickerHelper.swift │ ├── DXUtil.swift │ ├── UICollectionView+Convenience.swift │ ├── UIColor+DXHex.swift │ ├── UIView+DXFrame.swift │ └── UIViewController+DXPhotoPicker.swift ├── DXPhotoPickerDemo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── DXPhotoPickerDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-29@2x.png │ │ ├── Icon-29@3x.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-60@2x.png │ │ └── Icon-60@3x.png │ ├── Contents.json │ ├── back.imageset │ │ ├── Contents.json │ │ └── back.png │ └── default.imageset │ │ ├── Contents.json │ │ └── default.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── DXSelectedImageCell.swift ├── DXSelectedImageViewController.swift ├── Info.plist └── ViewController.swift ├── LICENSE ├── README.md └── ScreenShot ├── Icon.png └── ScreenShot.gif /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | # Carthage/Checkouts 32 | 33 | Carthage/Build 34 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Controllers/DXAlbumTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXAlbumTableViewController.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by DingXiao on 15/10/16. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class DXAlbumTableViewController: UITableViewController { 13 | 14 | let dxalbumTableViewCellReuseIdentifier = "dxalbumTableViewCellReuseIdentifier" 15 | 16 | var assetsCollection: [DXAlbum]? 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | title = DXlocalized(string: "albumTitle", comment: "photos") 21 | createBarButtonItemAt(position: .right, 22 | text: DXlocalized(string: "cancel", comment: "取消"), 23 | action: #selector(DXAlbumTableViewController.cancelAction) 24 | ) 25 | assetsCollection = DXPickerHelper.fetchAlbumList() 26 | self.tableView.register(DXAlbumCell.self, forCellReuseIdentifier: dxalbumTableViewCellReuseIdentifier) 27 | 28 | self.tableView.tableFooterView = UIView() 29 | } 30 | 31 | // MARK: public 32 | 33 | func reloadTableView() { 34 | assetsCollection = DXPickerHelper.fetchAlbumList() 35 | tableView.reloadData() 36 | } 37 | 38 | func showUnAuthorizedTipsView() { 39 | tableView.backgroundView = DXUnAuthorizedTipsView(frame: tableView.bounds) 40 | } 41 | 42 | // MARK: UIActions 43 | 44 | @objc private func cancelAction() { 45 | let photoPicker = navigationController as! DXPhotoPickerController 46 | photoPicker.photoPickerDelegate?.photoPickerDidCancel?(photoPicker: photoPicker) 47 | } 48 | 49 | // MARK: - Table view data source 50 | 51 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 52 | guard assetsCollection != nil else { 53 | return 0 54 | } 55 | return assetsCollection!.count 56 | } 57 | 58 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 59 | let cell = tableView.dequeueReusableCell(withIdentifier: dxalbumTableViewCellReuseIdentifier, for: indexPath) as! DXAlbumCell 60 | let album = assetsCollection![indexPath.row] 61 | cell.titleLabel.text = album.name 62 | cell.countLabel.text = "(\(album.count))" 63 | DXPickerHelper.fetchImage(viaAsset: album.results!.lastObject as? PHAsset, targetSize: CGSize(width:60, height:60)) { (image) -> Void in 64 | cell.posterImageView.image = image 65 | } 66 | return cell 67 | } 68 | 69 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 70 | return 60 71 | } 72 | 73 | override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { 74 | return 60 75 | } 76 | 77 | 78 | // MARK: - Table view data delegate 79 | 80 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 81 | let album = assetsCollection![indexPath.row] 82 | let photosListViewController = DXImageFlowViewController(album: album) 83 | navigationController?.pushViewController(photosListViewController, animated: true) 84 | tableView.deselectRow(at: indexPath as IndexPath, animated: true) 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Controllers/DXImageFlowViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXImageFlowViewController.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by Ding Xiao on 15/10/23. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class DXImageFlowViewController: UIViewController, UIScrollViewDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, DXPhotoBroswerDelegate { 13 | 14 | struct DXImageFlowConfig { 15 | static let dxAssetCellReuseIdentifier = "dxAssetCellReuseIdentifier" 16 | static let kThumbSizeLength = (UIScreen.main.bounds.size.width-10)/4 17 | } 18 | 19 | private var currentAlbum: DXAlbum? 20 | private var albumIdentifier: String? 21 | private var assetsArray: [PHAsset] 22 | private var selectedAssetsArray: [PHAsset] 23 | private var isFullImage = false 24 | private var imageManager: PHCachingImageManager? 25 | private var previousPreheatRect: CGRect = CGRect.zero 26 | 27 | private lazy var imageFlowCollectionView: UICollectionView = { 28 | let flowLayout = UICollectionViewFlowLayout() 29 | flowLayout.minimumLineSpacing = 2.0 30 | flowLayout.minimumInteritemSpacing = 2.0 31 | flowLayout.scrollDirection = .vertical; 32 | let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: flowLayout) 33 | collectionView.translatesAutoresizingMaskIntoConstraints = false 34 | collectionView.delegate = self 35 | collectionView.dataSource = self 36 | collectionView.alwaysBounceVertical = true 37 | collectionView.showsHorizontalScrollIndicator = true 38 | collectionView.backgroundColor = UIColor.white 39 | collectionView.register(DXAssetCell.self, forCellWithReuseIdentifier: DXImageFlowConfig.dxAssetCellReuseIdentifier) 40 | return collectionView; 41 | }() 42 | private lazy var sendButton: DXSendButton = { 43 | let button = DXSendButton(frame: CGRect.zero) 44 | button.addTarget(target: self, action: #selector(DXImageFlowViewController.sendImage)) 45 | return button 46 | }() 47 | 48 | // MARK: Initializers 49 | 50 | init(album: DXAlbum?) { 51 | currentAlbum = album 52 | assetsArray = [] 53 | selectedAssetsArray = [] 54 | super.init(nibName: nil, bundle: nil) 55 | } 56 | 57 | init(identifier: String?){ 58 | albumIdentifier = identifier 59 | assetsArray = [] 60 | selectedAssetsArray = [] 61 | super.init(nibName: nil, bundle: nil) 62 | } 63 | 64 | required init?(coder aDecoder: NSCoder) { 65 | currentAlbum = DXAlbum() 66 | assetsArray = [] 67 | selectedAssetsArray = [] 68 | super.init(coder: aDecoder) 69 | } 70 | 71 | deinit { 72 | resetCachedAssets() 73 | } 74 | 75 | // MARK: Life Cycle 76 | override func viewDidLoad() { 77 | super.viewDidLoad() 78 | func setupView() { 79 | view.backgroundColor = UIColor.white 80 | self.createBarButtonItemAt(position: .left, 81 | normalImage: UIImage(named: "back_normal"), 82 | highlightImage: UIImage(named: "back_highlight"), 83 | action: #selector(DXImageFlowViewController.backButtonAction)) 84 | self.createBarButtonItemAt(position: .right, 85 | text: DXlocalized(string: "cancel", comment: "取消"), 86 | action: #selector(DXImageFlowViewController.cancelAction)) 87 | 88 | let item1 = UIBarButtonItem( 89 | title: DXlocalized(string: "preview", comment: "预览"), 90 | style: .plain, 91 | target: self, 92 | action: #selector(DXImageFlowViewController.previewAction) 93 | ) 94 | item1.tintColor = UIColor.black 95 | item1.isEnabled = false 96 | let item2 = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) 97 | let item3 = UIBarButtonItem(customView: self.sendButton) 98 | let item4 = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) 99 | item4.width = -10 100 | setToolbarItems([item1,item2,item3,item4], animated: false) 101 | self.view.addSubview(imageFlowCollectionView) 102 | let viewBindDic = ["imageFlowCollectionView":imageFlowCollectionView] 103 | let vflH = "H:|-0-[imageFlowCollectionView]-0-|" 104 | let vflV = "V:|-0-[imageFlowCollectionView]-0-|" 105 | let contraintsH = NSLayoutConstraint.constraints(withVisualFormat: vflH, 106 | options: NSLayoutFormatOptions(rawValue: 0), 107 | metrics: nil, 108 | views: viewBindDic) 109 | let contraintsV = NSLayoutConstraint.constraints(withVisualFormat: vflV, 110 | options: NSLayoutFormatOptions(rawValue: 0), 111 | metrics: nil, 112 | views: viewBindDic) 113 | self.view.addConstraints(contraintsH) 114 | self.view.addConstraints(contraintsV) 115 | } 116 | 117 | func setUpData() { 118 | if currentAlbum == nil && albumIdentifier != nil { 119 | currentAlbum = DXPickerHelper.fetchAlbum() 120 | } 121 | self.title = currentAlbum?.name 122 | 123 | DispatchQueue.global().async { 124 | self.assetsArray = DXPickerHelper.fetchImageAssets(inCollectionResults: self.currentAlbum!.results) 125 | DispatchQueue.main.async { 126 | self.imageManager = PHCachingImageManager() 127 | self.imageFlowCollectionView.reloadData() 128 | let item = self.imageFlowCollectionView.numberOfItems(inSection: 0) 129 | guard item != 0 else { 130 | return 131 | } 132 | let lastItemIndex = IndexPath(item: item-1, section: 0) 133 | self.imageFlowCollectionView.scrollToItem(at: lastItemIndex, at: .bottom, animated: false) 134 | } 135 | } 136 | } 137 | setupView() 138 | setUpData() 139 | } 140 | 141 | override func viewWillAppear(_ animated: Bool) { 142 | super.viewWillAppear(animated) 143 | navigationController?.isToolbarHidden = false 144 | sendButton.badgeValue = "\(selectedAssetsArray.count)" 145 | } 146 | 147 | override func viewDidAppear(_ animated: Bool) { 148 | super.viewDidAppear(animated) 149 | updateCachedAssets() 150 | } 151 | 152 | override func viewWillDisappear(_ animated: Bool) { 153 | navigationController?.isToolbarHidden = true 154 | super.viewWillDisappear(animated) 155 | } 156 | 157 | // MARK: button actons 158 | 159 | @objc private func sendImage() { 160 | 161 | let photoPicker = navigationController as? DXPhotoPickerController 162 | guard (photoPicker != nil ) else { 163 | return 164 | } 165 | DXPickerHelper.save(identifier: currentAlbum?.identifier) 166 | DXLog(currentAlbum?.identifier) 167 | photoPicker!.photoPickerDelegate?.photoPickerController?(photoPicker: photoPicker, sendImages: selectedAssetsArray, isFullImage: isFullImage) 168 | } 169 | 170 | @objc private func backButtonAction() { 171 | self.pop(animated: true) 172 | } 173 | 174 | @objc private func cancelAction() { 175 | let navController = navigationController as? DXPhotoPickerController 176 | navController?.photoPickerDelegate?.photoPickerDidCancel?(photoPicker: navController!) 177 | } 178 | 179 | @objc private func previewAction() { 180 | browserPhotoAsstes(assets: selectedAssetsArray, pageIndex: 0) 181 | } 182 | 183 | // MARK: priviate 184 | 185 | private func browserPhotoAsstes(assets: [PHAsset], pageIndex: Int) { 186 | let browser = DXPhotoBrowser(photosArray: assets, currentIndex: pageIndex, isFullImage: isFullImage) 187 | browser.delegate = self 188 | browser.hidesBottomBarWhenPushed = true 189 | navigationController?.pushViewController(browser, animated: true) 190 | } 191 | 192 | private func addAsset(asset: PHAsset) -> Bool { 193 | if selectedAssetsArray.count >= DXPhotoPickerController.DXPhotoPickerConfig.maxSeletedNumber { 194 | showTips() 195 | return false 196 | } 197 | if selectedAssetsArray.contains(asset) { 198 | return false; 199 | } 200 | selectedAssetsArray.append(asset) 201 | sendButton.badgeValue = "\(self.selectedAssetsArray.count)" 202 | if selectedAssetsArray.count > 0 { 203 | toolbarItems!.first!.isEnabled = true 204 | } 205 | return true 206 | } 207 | 208 | @discardableResult 209 | private func deleteAsset(asset: PHAsset) -> Bool { 210 | if selectedAssetsArray.contains(asset) { 211 | let index = selectedAssetsArray.index(of: asset) 212 | guard index != nil else { 213 | return false 214 | } 215 | selectedAssetsArray.remove(at: index!) 216 | sendButton.badgeValue = "\(self.selectedAssetsArray.count)" 217 | if selectedAssetsArray.count <= 0 { 218 | toolbarItems!.first!.isEnabled = false 219 | } 220 | return true 221 | } 222 | return false 223 | } 224 | 225 | private func showTips() { 226 | let alertString = NSString(format: DXlocalized(string: "alertContent", comment: "") as NSString, NSNumber(value: DXPhotoPickerController.DXPhotoPickerConfig.maxSeletedNumber)) 227 | let alert = UIAlertController(title: DXlocalized(string: "alertTitle", comment: ""), message: alertString as String, preferredStyle: .alert) 228 | let action = UIAlertAction(title: DXlocalized(string: "alertButton", comment: ""), style: .cancel) { (action) -> Void in 229 | alert.dismiss(animated: true, completion: nil) 230 | } 231 | alert.addAction(action) 232 | navigationController?.present(alert, animated: true, completion: nil) 233 | } 234 | 235 | private func displayImageInCell(cell: DXAssetCell, indexPath: NSIndexPath) { 236 | cell.fillWithAsset(asset: assetsArray[indexPath.row], isAssetSelected: selectedAssetsArray.contains(assetsArray[indexPath.row])) 237 | let options = PHImageRequestOptions() 238 | options.resizeMode = PHImageRequestOptionsResizeMode.exact 239 | let scale = UIScreen.main.scale 240 | let size = CGSize(width: DXImageFlowConfig.kThumbSizeLength*scale, height: DXImageFlowConfig.kThumbSizeLength*scale); 241 | imageManager?.requestImage(for: assetsArray[indexPath.row], targetSize: size, contentMode: .aspectFill, options: options, resultHandler: { (image, obj) -> Void in 242 | DispatchQueue.main.async { 243 | cell.imageView.image = image 244 | } 245 | }) 246 | cell.selectItemBlock {[unowned self] (selected, asset) -> Bool in 247 | if selected == true { 248 | return self.addAsset(asset: asset) 249 | } else { 250 | self.deleteAsset(asset: asset) 251 | return false 252 | } 253 | } 254 | } 255 | 256 | // MARK: DXPhotoBroswerDelegate 257 | 258 | func sendImagesFromPhotoBrowser(photoBrowser: DXPhotoBrowser, currentAsset: PHAsset?) { 259 | sendImage() 260 | } 261 | 262 | func seletedPhotosNumberInPhotoBrowser(photoBrowser: DXPhotoBrowser) -> Int { 263 | return selectedAssetsArray.count 264 | } 265 | 266 | func photoBrowser(photoBrowser: DXPhotoBrowser, currentPhotoAssetIsSeleted asset: PHAsset?) -> Bool { 267 | guard asset != nil else { 268 | return false 269 | } 270 | 271 | return selectedAssetsArray.contains(asset!) 272 | } 273 | 274 | func photoBrowser(photoBrowser: DXPhotoBrowser, seletedAsset asset: PHAsset?) -> Bool { 275 | guard asset != nil else { 276 | return false 277 | } 278 | let index = assetsArray.index(of: asset!) 279 | guard index != nil else { 280 | return false 281 | } 282 | let success = addAsset(asset: asset!) 283 | imageFlowCollectionView.reloadItems(at: [IndexPath(item: index!, section: 0)]) 284 | return success 285 | } 286 | 287 | func photoBrowser(photoBrowser: DXPhotoBrowser, deseletedAsset asset: PHAsset?) { 288 | guard asset != nil else { 289 | return 290 | } 291 | 292 | let index = assetsArray.index(of: asset!) 293 | guard index != nil else { 294 | return 295 | } 296 | deleteAsset(asset: asset!) 297 | imageFlowCollectionView.reloadItems(at: [IndexPath(item: index!, section: 0)]) 298 | } 299 | 300 | func photoBrowser(photoBrowser: DXPhotoBrowser, seleteFullImage fullImage: Bool) { 301 | isFullImage = fullImage 302 | } 303 | 304 | 305 | // MARK: UICollectionViewDataSource 306 | 307 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 308 | return assetsArray.count 309 | } 310 | 311 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 312 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DXImageFlowConfig.dxAssetCellReuseIdentifier, for: indexPath) as! DXAssetCell 313 | displayImageInCell(cell: cell, indexPath: indexPath as NSIndexPath) 314 | return cell 315 | } 316 | 317 | // MARK: UICollectionViewDelegateFlowLayout 318 | 319 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 320 | return CGSize(width: DXImageFlowConfig.kThumbSizeLength, height: DXImageFlowConfig.kThumbSizeLength) 321 | } 322 | 323 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { 324 | return UIEdgeInsetsMake(2, 2, 2, 2) 325 | } 326 | 327 | // MARK: UICollectionViewDelegate 328 | 329 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 330 | browserPhotoAsstes(assets: assetsArray, pageIndex: indexPath.row) 331 | } 332 | 333 | // MARK: UIScrollViewDelegate 334 | 335 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 336 | updateCachedAssets() 337 | } 338 | 339 | // MARK: Asset Caching 340 | 341 | private func resetCachedAssets() { 342 | imageManager?.stopCachingImagesForAllAssets() 343 | previousPreheatRect = CGRect.zero 344 | } 345 | 346 | private func updateCachedAssets() { 347 | let isViewVisible = self.isViewLoaded && (self.view.window != nil) 348 | if isViewVisible == false { 349 | return 350 | } 351 | // The preheat window is twice the height of the visible rect. 352 | var preheatRect = self.imageFlowCollectionView.bounds 353 | preheatRect = preheatRect.insetBy(dx: 0.0, dy: -0.5 * preheatRect.height) 354 | /* 355 | Check if the collection view is showing an area that is significantly 356 | different to the last preheated area. 357 | */ 358 | let delta = fabs(preheatRect.midY - self.previousPreheatRect.midY) 359 | 360 | if (delta > self.imageFlowCollectionView.bounds.height / 3.0) { 361 | // Compute the assets to start caching and to stop caching. 362 | var addedIndexPaths = [NSIndexPath]() 363 | var removedIndexPaths = [NSIndexPath]() 364 | computeDifferenceBetweenRect(oldRect: previousPreheatRect, 365 | newRect: preheatRect, 366 | removedHandler: {[unowned self] (removedRect) ->Void in 367 | let indexPaths = self.imageFlowCollectionView.aapl_indexPathsForElementsInRect(rect: removedRect) 368 | if indexPaths != nil { 369 | removedIndexPaths.append(contentsOf: indexPaths!) 370 | } 371 | }, 372 | addedHandler: {[unowned self] (addedRect) -> Void in 373 | let indexPaths = self.imageFlowCollectionView.aapl_indexPathsForElementsInRect(rect: addedRect) 374 | if indexPaths != nil { 375 | addedIndexPaths.append(contentsOf: indexPaths!) 376 | } 377 | }) 378 | 379 | let assetsToStartCaching = assetsAtIndexPaths(indexPaths: addedIndexPaths) 380 | let assetsToStopCaching = assetsAtIndexPaths(indexPaths: removedIndexPaths) 381 | // Update the assets the PHCachingImageManager is caching. 382 | let options = PHImageRequestOptions() 383 | options.resizeMode = PHImageRequestOptionsResizeMode.exact 384 | let scale = UIScreen.main.scale 385 | let size = CGSize(width: DXImageFlowConfig.kThumbSizeLength*scale, height: DXImageFlowConfig.kThumbSizeLength*scale); 386 | 387 | if assetsToStartCaching != nil && (assetsToStartCaching?.count)! > 0 { 388 | self.imageManager?.startCachingImages(for: assetsToStartCaching!, targetSize: size, contentMode: .aspectFill, options: options) 389 | } 390 | if assetsToStopCaching != nil && (assetsToStopCaching?.count)! > 0 { 391 | self.imageManager?.stopCachingImages(for: assetsToStopCaching!, targetSize: size, contentMode: .aspectFill, options: options) 392 | } 393 | // Store the preheat rect to compare against in the future. 394 | self.previousPreheatRect = preheatRect; 395 | } 396 | 397 | } 398 | 399 | private func computeDifferenceBetweenRect( 400 | oldRect: CGRect, 401 | newRect: CGRect, 402 | removedHandler:(_ removedRect: CGRect)-> Void, 403 | addedHandler:(_ addedRect: CGRect)->Void) { 404 | if newRect.intersects(oldRect) { 405 | let oldMaxY = oldRect.maxY 406 | let oldMinY = oldRect.minY 407 | let newMaxY = newRect.maxY 408 | let newMinY = newRect.minY 409 | if newMaxY > oldMaxY { 410 | let rectToAdd = CGRect(x: newRect.origin.x, y: oldMaxY, width: newRect.size.width, height: (newMaxY - oldMaxY)) 411 | addedHandler(rectToAdd) 412 | } 413 | 414 | if oldMinY > newMinY { 415 | let rectToAdd = CGRect(x: newRect.origin.x, y: newMinY, width: newRect.size.width, height: (oldMinY - newMinY)) 416 | addedHandler(rectToAdd) 417 | } 418 | if newMaxY < oldMaxY { 419 | let rectToRemove = CGRect(x: newRect.origin.x, y: newMaxY, width: newRect.size.width, height: (oldMaxY - newMaxY)) 420 | removedHandler(rectToRemove) 421 | } 422 | if oldMinY < newMinY { 423 | let rectToRemove = CGRect(x: newRect.origin.x, y: oldMinY, width: newRect.size.width, height: (newMinY - oldMinY)) 424 | removedHandler(rectToRemove) 425 | } 426 | } else { 427 | addedHandler(newRect) 428 | removedHandler(oldRect) 429 | } 430 | } 431 | 432 | private func assetsAtIndexPaths(indexPaths: [NSIndexPath]) -> [PHAsset]? { 433 | if indexPaths.count == 0 { 434 | return nil; 435 | } 436 | var assets = [PHAsset]() 437 | for (_, results) in indexPaths.enumerated() { 438 | let asset = assetsArray[results.item] 439 | assets.append(asset) 440 | } 441 | return assets 442 | } 443 | 444 | } 445 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Controllers/DXPhotoBrowser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXPhotoBroswer.swift 3 | // DXPhotosPickerDemo 4 | // 5 | // Created by Ding Xiao on 15/10/14. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | protocol DXPhotoBroswerDelegate: NSObjectProtocol { 13 | 14 | func sendImagesFromPhotoBrowser(photoBrowser: DXPhotoBrowser, currentAsset: PHAsset?) 15 | func seletedPhotosNumberInPhotoBrowser(photoBrowser: DXPhotoBrowser) -> Int 16 | func photoBrowser(photoBrowser: DXPhotoBrowser, currentPhotoAssetIsSeleted asset: PHAsset?) -> Bool 17 | func photoBrowser(photoBrowser: DXPhotoBrowser, seletedAsset asset: PHAsset?) -> Bool 18 | func photoBrowser(photoBrowser: DXPhotoBrowser, deseletedAsset asset: PHAsset?) 19 | func photoBrowser(photoBrowser: DXPhotoBrowser, seleteFullImage fullImage: Bool) 20 | } 21 | 22 | class DXPhotoBrowser: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { 23 | 24 | struct DXPhotoBrowserConfig { 25 | static let browserCellReuseIdntifier = "DXBrowserCell" 26 | } 27 | 28 | // MARK: peoperties 29 | private var statusBarShouldBeHidden = false 30 | private var didSavePreviousStateOfNavBar = false 31 | private var viewIsActive = false 32 | private var viewHasAppearedInitially = false 33 | private var previousNavBarHidden = false 34 | private var previousNavBarTranslucent = false 35 | private var previousNavBarStyle: UIBarStyle = .default 36 | private var previousStatusBarStyle: UIStatusBarStyle = .default 37 | private var previousNavBarTintColor: UIColor? 38 | private var previousNavBarBarTintColor: UIColor? 39 | private var previousViewControllerBackButton: UIBarButtonItem? 40 | private var previousNavigationBarBackgroundImageDefault: UIImage? 41 | private var previousNavigationBarBackgroundImageLandscapePhone: UIImage? 42 | 43 | private var photosDataSource: Array? 44 | private var currentIndex = 0 45 | private var fullImage = false 46 | private var requestID: PHImageRequestID? 47 | 48 | lazy var fullImageButton: DXFullImageButton = { 49 | 50 | let button = DXFullImageButton(frame: CGRect(x: 0, y: 0, width: self.view.dx_width/2, height: 28)) 51 | button.addTarget(target: self, action: #selector(DXPhotoBrowser.fullImageButtonAction)) 52 | button.selected = self.fullImage 53 | return button 54 | }() 55 | 56 | lazy var browserCollectionView: UICollectionView = { 57 | let layout = UICollectionViewFlowLayout() 58 | layout.minimumInteritemSpacing = 0 59 | layout.minimumLineSpacing = 0 60 | layout.scrollDirection = .horizontal 61 | 62 | let collectionView = UICollectionView(frame: CGRect(x: -10, y: 0, width: self.view.dx_width+20, height: self.view.dx_height), collectionViewLayout: layout) 63 | collectionView.backgroundColor = UIColor.black 64 | collectionView.register(DXBrowserCell.self, forCellWithReuseIdentifier: DXPhotoBrowserConfig.browserCellReuseIdntifier) 65 | collectionView.delegate = self 66 | collectionView.dataSource = self 67 | collectionView.isPagingEnabled = true 68 | collectionView.showsHorizontalScrollIndicator = false 69 | collectionView.showsVerticalScrollIndicator = false 70 | return collectionView 71 | }() 72 | 73 | lazy var toolBar: UIToolbar = { 74 | let toolbar = UIToolbar(frame: CGRect(x: 0, y:self.view.dx_height - 44, width:self.view.dx_width, height:44)) 75 | toolbar.setBackgroundImage(nil, forToolbarPosition: .any, barMetrics: .default) 76 | toolbar.barStyle = .black 77 | toolbar.isTranslucent = true 78 | return toolbar 79 | }() 80 | 81 | lazy var checkButton: UIButton = { 82 | let button = UIButton(type: .custom) 83 | button.frame = CGRect(x:0, y:0, width:25, height:25) 84 | button.setBackgroundImage(UIImage(named: "photo_check_selected"), for: .selected) 85 | button.setBackgroundImage(UIImage(named: "photo_check_default"), for: .normal) 86 | button.addTarget(self, action: #selector(DXPhotoBrowser.checkButtonAction), for: .touchUpInside) 87 | return button 88 | }() 89 | 90 | lazy var sendButton: DXSendButton = { 91 | let button = DXSendButton(frame: CGRect.zero) 92 | button.addTarget(target: self, action: #selector(DXPhotoBrowser.sendButtonAction)) 93 | return button 94 | }() 95 | 96 | weak var delegate: DXPhotoBroswerDelegate? 97 | 98 | // MARK: life time 99 | required init(photosArray: Array?, currentIndex: Int, isFullImage: Bool) { 100 | self.init() 101 | self.currentIndex = currentIndex 102 | fullImage = isFullImage 103 | photosDataSource = photosArray as? Array 104 | } 105 | 106 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 107 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil); 108 | } 109 | 110 | required init?(coder aDecoder: NSCoder) { 111 | super.init(coder: aDecoder) 112 | } 113 | 114 | // MARK: life cycle 115 | 116 | override func viewDidLoad() { 117 | super.viewDidLoad() 118 | setupViews() 119 | updateNavigationBarAndToolBar() 120 | updateSelestedNumber() 121 | } 122 | 123 | override func viewWillAppear(_ animated: Bool) { 124 | super.viewWillAppear(animated) 125 | self.previousStatusBarStyle = UIApplication.shared.statusBarStyle 126 | UIApplication.shared.setStatusBarStyle(.default, animated: animated) 127 | 128 | // Navigation bar appearance 129 | if (viewIsActive == false && navigationController?.viewControllers.first != self) { 130 | self.storePreviousNavBarAppearance() 131 | } 132 | self.setNavBarAppearance(animated: animated) 133 | if self.viewHasAppearedInitially == false { 134 | self.viewHasAppearedInitially = true 135 | } 136 | browserCollectionView.contentOffset = CGPoint(x:browserCollectionView.dx_width * CGFloat(currentIndex), y:0) 137 | } 138 | 139 | override func viewWillDisappear(_ animated: Bool) { 140 | if (navigationController?.viewControllers.first != self && navigationController?.viewControllers.contains(self) == false) { 141 | viewIsActive = false 142 | restorePreviousNavBarAppearance(animated: animated) 143 | } 144 | navigationController?.navigationBar.layer.removeAllAnimations() 145 | NSObject.cancelPreviousPerformRequests(withTarget: self) 146 | setControlsHidden(hidden: false, animated: false) 147 | UIApplication.shared.setStatusBarStyle(previousStatusBarStyle, animated: animated) 148 | super.viewWillDisappear(animated) 149 | } 150 | 151 | override func viewDidAppear(_ animated: Bool) { 152 | super.viewDidAppear(animated) 153 | viewIsActive = true 154 | } 155 | 156 | // MARK: priviate 157 | 158 | private func restorePreviousNavBarAppearance(animated: Bool) { 159 | if didSavePreviousStateOfNavBar == true { 160 | navigationController?.setNavigationBarHidden(previousNavBarHidden, animated: animated) 161 | let navBar = navigationController!.navigationBar 162 | navBar.tintColor = previousNavBarBarTintColor 163 | navBar.isTranslucent = previousNavBarTranslucent 164 | navBar.barTintColor = previousNavBarBarTintColor 165 | navBar.barStyle = previousNavBarStyle 166 | navBar.setBackgroundImage(previousNavigationBarBackgroundImageDefault, for: .default) 167 | navBar.setBackgroundImage(previousNavigationBarBackgroundImageLandscapePhone, for: .compact) 168 | if previousViewControllerBackButton != nil { 169 | let previousViewController = navigationController!.topViewController 170 | previousViewController?.navigationItem.backBarButtonItem = previousViewControllerBackButton 171 | previousViewControllerBackButton = nil 172 | } 173 | } 174 | } 175 | 176 | private func setNavBarAppearance(animated: Bool) { 177 | navigationController?.setNavigationBarHidden(false, animated: animated) 178 | let navBar = navigationController!.navigationBar 179 | navBar.tintColor = UIColor.white 180 | navBar.isTranslucent = true 181 | navBar.barStyle = .blackTranslucent 182 | navBar.setBackgroundImage(nil, for: .default) 183 | navBar.setBackgroundImage(nil, for: .compact) 184 | } 185 | 186 | private func storePreviousNavBarAppearance() { 187 | didSavePreviousStateOfNavBar = true 188 | previousNavBarBarTintColor = navigationController?.navigationBar.barTintColor 189 | previousNavBarTranslucent = navigationController!.navigationBar.isTranslucent; 190 | previousNavBarTintColor = navigationController!.navigationBar.tintColor; 191 | previousNavBarHidden = navigationController!.isNavigationBarHidden; 192 | previousNavBarStyle = navigationController!.navigationBar.barStyle; 193 | previousNavigationBarBackgroundImageDefault = navigationController!.navigationBar.backgroundImage(for: .default) 194 | previousNavigationBarBackgroundImageLandscapePhone = navigationController!.navigationBar.backgroundImage(for: .compact) 195 | } 196 | 197 | private func setupViews() { 198 | 199 | func setupBarButtonItems() { 200 | let item1 = UIBarButtonItem(customView: fullImageButton) 201 | let item2 = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) 202 | let item3 = UIBarButtonItem(customView: sendButton) 203 | let item4 = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) 204 | item4.width = -10 205 | toolBar.items = [item1,item2,item3,item4] 206 | } 207 | 208 | automaticallyAdjustsScrollViewInsets = false 209 | view.clipsToBounds = true 210 | view.addSubview(browserCollectionView) 211 | view.addSubview(toolBar) 212 | setupBarButtonItems() 213 | let rigthBarItem = UIBarButtonItem(customView: checkButton) 214 | navigationItem.rightBarButtonItem = rigthBarItem 215 | self.createBarButtonItemAt(position: .left, normalImage: UIImage(named: "back_normal"), highlightImage: UIImage(named: "back_highlight"), action: #selector(DXPhotoBrowser.backButtonAction)) 216 | } 217 | 218 | private func updateNavigationBarAndToolBar() { 219 | guard photosDataSource != nil else { 220 | return 221 | } 222 | title = "\(currentIndex + 1)"+"/" + "\(photosDataSource!.count)" 223 | let asset = photosDataSource?[currentIndex] 224 | let selected = self.delegate?.photoBrowser(photoBrowser: self, currentPhotoAssetIsSeleted: asset) 225 | if selected == nil { 226 | checkButton.isSelected = false 227 | } else { 228 | checkButton.isSelected = selected! 229 | } 230 | 231 | fullImageButton.selected = fullImage 232 | if fullImage { 233 | let asset = photosDataSource![currentIndex] 234 | DXPickerHelper.fetchImageSize(asset: asset, imageSizeResultHandler: {[unowned self] (imageSize, sizeString) -> Void in 235 | self.fullImageButton.text = "(\(sizeString))" 236 | }) 237 | } 238 | } 239 | 240 | private func updateSelestedNumber() { 241 | let number = self.delegate?.seletedPhotosNumberInPhotoBrowser(photoBrowser: self) 242 | if (number != nil) { 243 | self.sendButton.badgeValue = "\(number!)" 244 | } 245 | 246 | } 247 | 248 | private func didScrollToPage(page: Int) { 249 | currentIndex = page 250 | updateNavigationBarAndToolBar() 251 | } 252 | 253 | // MARK: ui actions 254 | 255 | @objc private func backButtonAction() { 256 | self.pop(animated: true) 257 | } 258 | 259 | @objc private func sendButtonAction() { 260 | delegate?.sendImagesFromPhotoBrowser(photoBrowser: self, currentAsset: photosDataSource![currentIndex]) 261 | } 262 | 263 | @objc private func fullImageButtonAction() { 264 | fullImageButton.selected = !fullImageButton.selected 265 | fullImage = fullImageButton.selected 266 | self.delegate?.photoBrowser(photoBrowser: self, seleteFullImage: fullImage) 267 | if fullImageButton.selected { 268 | let asset = photosDataSource?[currentIndex] 269 | if delegate?.photoBrowser(photoBrowser: self, seletedAsset: asset) != nil { 270 | updateNavigationBarAndToolBar() 271 | updateSelestedNumber() 272 | } 273 | } 274 | } 275 | 276 | @objc private func checkButtonAction() { 277 | let asset = photosDataSource?[currentIndex] 278 | if checkButton.isSelected { 279 | delegate?.photoBrowser(photoBrowser: self, deseletedAsset: asset) 280 | checkButton.isSelected = false 281 | updateSelestedNumber() 282 | } else { 283 | let selected = delegate?.photoBrowser(photoBrowser: self, seletedAsset: asset) 284 | if selected == nil { 285 | checkButton.isSelected = false 286 | } else { 287 | checkButton.isSelected = selected! 288 | } 289 | 290 | if checkButton.isSelected { 291 | updateSelestedNumber() 292 | } 293 | } 294 | } 295 | 296 | // MARK: ScrollerViewDelegate 297 | 298 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 299 | let deltaOffset = scrollView.contentOffset.x - browserCollectionView.dx_width * CGFloat(currentIndex) 300 | if (fabs(deltaOffset) >= browserCollectionView.dx_width/2 ) { 301 | fullImageButton.shouldAnimating(animated: true) 302 | } else { 303 | fullImageButton.shouldAnimating(animated: false) 304 | } 305 | } 306 | 307 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 308 | if scrollView.contentOffset.x >= 0 { 309 | let page = scrollView.contentOffset.x / browserCollectionView.dx_width 310 | didScrollToPage(page: Int(page)) 311 | } 312 | fullImageButton.shouldAnimating(animated: false) 313 | } 314 | 315 | // MARK: UICollectionViewDelegateFlowLayout 316 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 317 | return CGSize(width: self.view.bounds.size.width+20, height: self.view.bounds.size.height); 318 | } 319 | 320 | // MARK: UICollectionViewDataSource 321 | 322 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 323 | return self.photosDataSource == nil ? 0 : self.photosDataSource!.count 324 | } 325 | 326 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 327 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DXPhotoBrowserConfig.browserCellReuseIdntifier, for: indexPath) as! DXBrowserCell 328 | cell.asset = photosDataSource![indexPath.row] 329 | cell.photoBrowser = self 330 | return cell 331 | } 332 | 333 | // MARK: control hide 334 | 335 | private func setControlsHidden(hidden: Bool, animated: Bool) { 336 | var hide = hidden 337 | if (photosDataSource == nil || photosDataSource!.count == 0) { 338 | hide = false 339 | } 340 | let animationOffSet: CGFloat = 20 341 | let animationDuration = (animated ? 0.35 : 0) 342 | statusBarShouldBeHidden = hide 343 | UIView.animate(withDuration: animationDuration, animations: {[unowned self] () -> Void in 344 | self.setNeedsStatusBarAppearanceUpdate() 345 | }) 346 | let frame = CGRect(x:0, y:view.dx_height - 44, width:view.dx_width, height:44).integral 347 | if areControlsHidden() && hide == false && animated { 348 | toolBar.frame = frame.offsetBy(dx: 0, dy: animationOffSet) 349 | } 350 | UIView.animate(withDuration: animationDuration) {[unowned self] () -> Void in 351 | let alpha: CGFloat = hide ? 0 : 1 352 | self.navigationController?.navigationBar.alpha = alpha 353 | self.toolBar.frame = frame 354 | if hide { 355 | self.toolBar.frame = self.toolBar.frame.offsetBy(dx: 0, dy: animationOffSet) 356 | } 357 | self.toolBar.alpha = alpha 358 | } 359 | } 360 | 361 | 362 | override var prefersStatusBarHidden: Bool { 363 | return statusBarShouldBeHidden 364 | } 365 | 366 | override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { 367 | return .slide 368 | } 369 | 370 | private func areControlsHidden() -> Bool { 371 | return toolBar.alpha == 0 372 | } 373 | 374 | private func hideControls() { 375 | setControlsHidden(hidden: true, animated: true) 376 | } 377 | 378 | @objc func toggleControls() { 379 | setControlsHidden(hidden: !areControlsHidden(), animated: true) 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Controllers/DXPhotoPickerController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXPhototPickerController.swift 3 | // DXPhotoPicker 4 | // 5 | // Created by Ding Xiao on 15/10/13. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | @available(iOS 8.0, *) 13 | @objc public protocol DXPhotoPickerControllerDelegate: NSObjectProtocol { 14 | /** 15 | seletced call back 16 | 17 | - parameter photosPicker: the photoPicker 18 | - parameter sendImages: selected images 19 | - parameter isFullImage: if the selected image is high quality 20 | */ 21 | @objc optional func photoPickerController(photoPicker: DXPhotoPickerController?, sendImages: [PHAsset]?, isFullImage: Bool) 22 | 23 | /** 24 | cancel selected 25 | */ 26 | @objc optional func photoPickerDidCancel(photoPicker: DXPhotoPickerController) 27 | } 28 | 29 | @available(iOS 8.0, *) 30 | public class DXPhotoPickerController: UINavigationController, UINavigationControllerDelegate, UIGestureRecognizerDelegate { 31 | 32 | struct DXPhotoPickerConfig { 33 | /// set the max selected number 34 | static let maxSeletedNumber = 9 35 | } 36 | 37 | 38 | public weak var photoPickerDelegate: DXPhotoPickerControllerDelegate? 39 | 40 | override public func viewDidLoad() { 41 | super.viewDidLoad() 42 | self.interactivePopGestureRecognizer?.delegate = self 43 | self.interactivePopGestureRecognizer?.isEnabled = true 44 | 45 | func showAlbumList() { 46 | let viewController = DXAlbumTableViewController() 47 | self.viewControllers = [viewController] 48 | } 49 | 50 | func showImageFlow() { 51 | let rootVC = DXAlbumTableViewController() 52 | let imageFlowVC = DXImageFlowViewController(identifier: DXPickerHelper.fetchAlbumIdentifier()) 53 | self.viewControllers = [rootVC,imageFlowVC] 54 | } 55 | 56 | func chargeAuthorization(status: PHAuthorizationStatus) { 57 | 58 | let viewController = viewControllers.first as? DXAlbumTableViewController 59 | guard viewController != nil else { 60 | showAlbumList() 61 | return 62 | } 63 | switch (status) { 64 | case .authorized: 65 | DispatchQueue.main.async { 66 | viewController!.reloadTableView() 67 | } 68 | 69 | case .denied: 70 | DispatchQueue.main.async { 71 | viewController!.showUnAuthorizedTipsView() 72 | } 73 | 74 | case .restricted: 75 | DispatchQueue.main.async { 76 | viewController!.showUnAuthorizedTipsView() 77 | } 78 | 79 | case .notDetermined: 80 | PHPhotoLibrary.requestAuthorization({ (status) -> Void in 81 | guard status != .notDetermined else { 82 | return 83 | } 84 | DispatchQueue.main.async{ () -> Void in 85 | chargeAuthorization(status: status) 86 | } 87 | }) 88 | } 89 | 90 | } 91 | 92 | if DXPickerHelper.fetchAlbumIdentifier() == nil { 93 | showAlbumList() 94 | chargeAuthorization(status: PHPhotoLibrary.authorizationStatus()) 95 | } else { 96 | if DXPickerHelper.fetchAlbumIdentifier()!.isEmpty { 97 | showAlbumList() 98 | chargeAuthorization(status: PHPhotoLibrary.authorizationStatus()) 99 | } else { 100 | showImageFlow() 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Models/DXAlbum.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXAlbum.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by Ding Xiao on 15/10/21. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | /* 13 | @note use this model to store the album's 'result, 'count, 'name, 'startDate 14 | to avoid request and reserve too much times. 15 | */ 16 | 17 | class DXAlbum: NSObject { 18 | var results: PHFetchResult? 19 | var count = 0 20 | var name: String? 21 | var startDate: Date? 22 | var identifier: String? 23 | } 24 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Views/DXAlbumCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXAlbumCell.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by DingXiao on 15/10/21. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DXAlbumCell: UITableViewCell { 12 | 13 | lazy var posterImageView: UIImageView = { 14 | let imageView = UIImageView( 15 | image: UIImage(named: "assets_placeholder_picture") 16 | ) 17 | imageView.contentMode = UIViewContentMode.scaleAspectFill 18 | return imageView 19 | }() 20 | lazy var titleLabel: UILabel = { 21 | var label = UILabel(frame: CGRect.zero) 22 | label.backgroundColor = UIColor.white 23 | label.textColor = UIColor.darkText 24 | label.textAlignment = NSTextAlignment.left 25 | label.font = UIFont.boldSystemFont(ofSize: 16.0) 26 | return label 27 | }() 28 | lazy var countLabel: UILabel = { 29 | var label = UILabel(frame: CGRect.zero) 30 | label.backgroundColor = UIColor.white 31 | label.textColor = UIColor.lightGray 32 | label.textAlignment = NSTextAlignment.left 33 | label.font = UIFont.systemFont(ofSize: 14.0) 34 | return label 35 | }() 36 | 37 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 38 | super.init(style: style, reuseIdentifier: reuseIdentifier) 39 | accessoryType = UITableViewCellAccessoryType.disclosureIndicator 40 | self.setup() 41 | } 42 | 43 | required init?(coder aDecoder: NSCoder) { 44 | super.init(coder: aDecoder) 45 | self.setup() 46 | } 47 | 48 | private func setup() { 49 | 50 | self.separatorInset = UIEdgeInsetsMake(0,60,0,0) 51 | contentView.addSubview(posterImageView) 52 | contentView.addSubview(titleLabel) 53 | contentView.addSubview(countLabel) 54 | posterImageView.translatesAutoresizingMaskIntoConstraints = false 55 | titleLabel.translatesAutoresizingMaskIntoConstraints = false 56 | countLabel.translatesAutoresizingMaskIntoConstraints = false 57 | 58 | let viewBindingsDict = [ 59 | "posterImageView":posterImageView, 60 | "titleLabel": titleLabel, 61 | "countLabel": countLabel 62 | ] as [String : Any] 63 | let mertic = [ 64 | "imageLength": 60 65 | ] 66 | 67 | let vflH = "H:|-0-[posterImageView(imageLength)]-10-[titleLabel(10@750)]-5-[countLabel]-0-|" 68 | let imageVFLV = "V:|-0-[posterImageView(imageLength)]" 69 | let contstraintsH: Array = NSLayoutConstraint.constraints(withVisualFormat: vflH, 70 | options: NSLayoutFormatOptions.alignAllCenterY, 71 | metrics: mertic, 72 | views: viewBindingsDict) 73 | let imageContstraintsV: Array = NSLayoutConstraint.constraints(withVisualFormat: imageVFLV, 74 | options: NSLayoutFormatOptions(rawValue: 0), 75 | metrics: mertic, 76 | views: viewBindingsDict) 77 | let titleLabelHeightConstraint = NSLayoutConstraint( 78 | item: titleLabel, 79 | attribute: NSLayoutAttribute.height, 80 | relatedBy: NSLayoutRelation.equal, 81 | toItem: nil, 82 | attribute: NSLayoutAttribute.notAnAttribute, 83 | multiplier: 1, 84 | constant: 40 85 | ) 86 | let countLabelHeightConstraint = NSLayoutConstraint( 87 | item: countLabel, 88 | attribute: NSLayoutAttribute.height, 89 | relatedBy: NSLayoutRelation.equal, 90 | toItem: titleLabel, 91 | attribute: NSLayoutAttribute.height, 92 | multiplier: 1, 93 | constant: 0 94 | ) 95 | contentView.addConstraints(contstraintsH) 96 | contentView.addConstraints(imageContstraintsV) 97 | contentView.addConstraints([titleLabelHeightConstraint, countLabelHeightConstraint]) 98 | } 99 | 100 | override func awakeFromNib() { 101 | super.awakeFromNib() 102 | } 103 | 104 | override func setSelected(_ selected: Bool, animated: Bool) { 105 | super.setSelected(selected, animated: animated) 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Views/DXAssetCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXAssetCell.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by DingXiao on 15/10/26. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | @available(iOS 8.0, *) 13 | class DXAssetCell: UICollectionViewCell { 14 | 15 | // MARK: properties 16 | private var asset: PHAsset? 17 | 18 | private var selectItemBlock: ((Bool, PHAsset) -> Bool)? 19 | 20 | lazy var imageView: UIImageView = { 21 | let imv = UIImageView(image: UIImage(named: "assets_placeholder_picture")) 22 | imv.translatesAutoresizingMaskIntoConstraints = false 23 | return imv 24 | }() 25 | 26 | private lazy var checkButton: UIButton = { 27 | let button = UIButton(type: .custom) 28 | button.translatesAutoresizingMaskIntoConstraints = false 29 | button.addTarget(self, action: #selector(DXAssetCell.checkButtonAction), for: .touchUpInside) 30 | return button 31 | }() 32 | 33 | private lazy var checkImageView: UIImageView = { 34 | let imv = UIImageView(frame: CGRect.zero) 35 | imv.contentMode = .scaleAspectFit 36 | imv.translatesAutoresizingMaskIntoConstraints = false 37 | return imv 38 | }() 39 | 40 | private var assetSeleted: Bool = false 41 | 42 | // MARK: life time 43 | 44 | override init(frame: CGRect) { 45 | super.init(frame: frame) 46 | setupView() 47 | } 48 | 49 | required init?(coder aDecoder: NSCoder) { 50 | super.init(coder: aDecoder) 51 | setupView() 52 | } 53 | 54 | override func prepareForReuse() { 55 | self.assetSeleted = false 56 | if (self.imageView.image != nil) { 57 | self.imageView.image = nil 58 | } 59 | if self.asset != nil { 60 | self.asset = nil 61 | } 62 | 63 | } 64 | 65 | // MARK: public methods 66 | 67 | func fillWithAsset(asset: PHAsset, isAssetSelected: Bool) { 68 | self.asset = asset 69 | assetSeleted = isAssetSelected 70 | self.checkButton(selected: assetSeleted, animated: false) 71 | } 72 | 73 | func selectItemBlock(block: @escaping (_ selected: Bool, _ asset: PHAsset) -> Bool) { 74 | self.selectItemBlock = block 75 | } 76 | 77 | 78 | // MARK: convenience 79 | 80 | func checkButton(selected: Bool, animated: Bool) { 81 | if selected { 82 | self.checkImageView.image = UIImage(named: "photo_check_selected") 83 | if animated == false { 84 | return 85 | } 86 | UIView.animate(withDuration: 0.2, 87 | animations: { 88 | [unowned self] () -> Void in 89 | self.checkImageView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2);}, 90 | completion: { 91 | [unowned self](stop) -> Void in 92 | UIView.animate(withDuration: 0.2, animations: { [unowned self]() -> Void in 93 | self.checkImageView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0); 94 | }) 95 | }) 96 | } else { 97 | self.checkImageView.image = UIImage(named: "photo_check_default") 98 | } 99 | } 100 | 101 | private func setupView() { 102 | contentView.addSubview(imageView) 103 | contentView.addSubview(checkImageView) 104 | contentView.addSubview(checkButton) 105 | let viewBindingsDict = [ 106 | "posterImageView":imageView, 107 | "checkButton": checkButton, 108 | "checkImageView": checkImageView 109 | ] as [String : Any] 110 | let mertic = ["sideLength": 25] 111 | let imageViewVFLV = "V:|-0-[posterImageView]-0-|" 112 | let imageViewVFLH = "H:|-0-[posterImageView]-0-|" 113 | let imageViewContraintsV = NSLayoutConstraint.constraints( 114 | withVisualFormat: imageViewVFLV, 115 | options: NSLayoutFormatOptions(rawValue: 0), 116 | metrics: nil, 117 | views: viewBindingsDict 118 | ) 119 | let imageViewContraintsH = NSLayoutConstraint.constraints( 120 | withVisualFormat: imageViewVFLH, 121 | options: NSLayoutFormatOptions(rawValue: 0), 122 | metrics: nil, 123 | views: viewBindingsDict 124 | ) 125 | let checkImageViewVFLH = "H:[checkImageView(sideLength)]-3-|" 126 | let checkImageViewVFLV = "V:|-3-[checkImageView(sideLength)]" 127 | let checkImageViewContrainsH = NSLayoutConstraint.constraints( 128 | withVisualFormat: checkImageViewVFLH, 129 | options: NSLayoutFormatOptions(rawValue: 0), 130 | metrics: mertic, 131 | views: viewBindingsDict 132 | ) 133 | let checkImageViewContrainsV = NSLayoutConstraint.constraints( 134 | withVisualFormat: checkImageViewVFLV, 135 | options: NSLayoutFormatOptions(rawValue: 0), 136 | metrics: mertic, 137 | views: viewBindingsDict 138 | ) 139 | let checkConstraitRight = NSLayoutConstraint( 140 | item: checkButton, 141 | attribute: .trailing, 142 | relatedBy: .equal, 143 | toItem: contentView, 144 | attribute: .trailing, 145 | multiplier: 1.0, 146 | constant: 0 147 | ) 148 | let checkConstraitTop = NSLayoutConstraint( 149 | item: checkButton, 150 | attribute: .top, 151 | relatedBy: .equal, 152 | toItem: contentView, 153 | attribute: .top, 154 | multiplier: 1.0, 155 | constant: 0 156 | ) 157 | let checkContraitWidth = NSLayoutConstraint( 158 | item: checkButton, 159 | attribute: .width, 160 | relatedBy: .equal, 161 | toItem: imageView, 162 | attribute: .width, 163 | multiplier: 0.5, 164 | constant: 0 165 | ) 166 | let checkConsraintHeight = NSLayoutConstraint( 167 | item: checkButton, 168 | attribute: .height, 169 | relatedBy: .equal, 170 | toItem: checkButton, 171 | attribute: .height, 172 | multiplier: 1.0, 173 | constant: 0 174 | ) 175 | addConstraints(imageViewContraintsV) 176 | addConstraints(imageViewContraintsH) 177 | addConstraints(checkImageViewContrainsH) 178 | addConstraints(checkImageViewContrainsV) 179 | addConstraints([checkConstraitRight,checkConstraitTop,checkContraitWidth,checkConsraintHeight]) 180 | } 181 | 182 | // MARK:UI actions 183 | @objc private func checkButtonAction() { 184 | assetSeleted = !assetSeleted 185 | guard selectItemBlock != nil else { 186 | return 187 | } 188 | assetSeleted = selectItemBlock!(assetSeleted, asset!) 189 | self.checkButton(selected: assetSeleted, animated: true) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Views/DXBadgeLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXBadgeLabel.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by DingXiao on 15/10/16. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DXBadgeLabel: UIView { 12 | 13 | var title: String? 14 | 15 | private lazy var backgroundView: UIView = { 16 | let view = UIView(frame: CGRect.zero) 17 | view.backgroundColor = UIColor(hexColor: "#1FB823") 18 | view.layer.cornerRadius = 10 19 | return view 20 | }() 21 | 22 | private lazy var badgeLabel: UILabel = { 23 | let label = UILabel(frame: CGRect.zero) 24 | return label 25 | }() 26 | } 27 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Views/DXBrowserCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXBrowserCell.swift 3 | // DXPhotosPickerDemo 4 | // 5 | // Created by Ding Xiao on 15/10/14. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | @available(iOS 8.0, *) 13 | class DXBrowserCell: UICollectionViewCell, UIScrollViewDelegate, DXTapDetectingImageViewDelegate { 14 | 15 | // MARK: properties 16 | 17 | weak var photoBrowser: DXPhotoBrowser? 18 | var asset: PHAsset? { 19 | didSet { 20 | displayImage() 21 | } 22 | } 23 | 24 | private var requestID: PHImageRequestID? 25 | 26 | private lazy var zoomingScrollView: UIScrollView = { 27 | let scroll = UIScrollView(frame: CGRect(x: 10, y: 0, width: self.dx_width - 20, height: self.dx_height)) 28 | scroll.delegate = self 29 | scroll.showsHorizontalScrollIndicator = false 30 | scroll.showsVerticalScrollIndicator = false 31 | scroll.decelerationRate = UIScrollViewDecelerationRateFast 32 | return scroll 33 | }() 34 | 35 | private lazy var photoImageView: DXTapDetectingImageView = { 36 | let imageView = DXTapDetectingImageView(frame: CGRect.zero) 37 | imageView.tapDelegate = self 38 | imageView.contentMode = .scaleAspectFit 39 | imageView.backgroundColor = UIColor.black 40 | return imageView 41 | }() 42 | 43 | // MARK: life cycle 44 | override init(frame: CGRect) { 45 | super.init(frame: frame) 46 | setupView() 47 | } 48 | 49 | required init?(coder aDecoder: NSCoder) { 50 | super.init(coder: aDecoder) 51 | setupView() 52 | } 53 | 54 | override func prepareForReuse() { 55 | super.prepareForReuse() 56 | photoImageView.image = nil 57 | if requestID != nil { 58 | PHImageManager.default().cancelImageRequest(requestID!) 59 | requestID = nil 60 | } 61 | } 62 | 63 | override func layoutSubviews() { 64 | super.layoutSubviews() 65 | // Center the image as it becomes smaller than the size of the screen 66 | photoImageView.center = CGPoint(x: self.zoomingScrollView.dx_width/2, y: self.zoomingScrollView.dx_height/2) 67 | 68 | // Center the image as it becomes smaller than the size of the screen 69 | let boundsSize = zoomingScrollView.dx_size 70 | var frameToCenter = photoImageView.frame 71 | 72 | // Horizontally 73 | if (frameToCenter.size.width < boundsSize.width) { 74 | frameToCenter.origin.x = floor((boundsSize.width - frameToCenter.size.width) / 2.0); 75 | } else { 76 | frameToCenter.origin.x = 0; 77 | } 78 | 79 | // Vertically 80 | if (frameToCenter.size.height < boundsSize.height) { 81 | frameToCenter.origin.y = floor((boundsSize.height - frameToCenter.size.height) / 2.0); 82 | } else { 83 | frameToCenter.origin.y = 0; 84 | } 85 | 86 | // Center 87 | if (!photoImageView.frame.equalTo(frameToCenter)) { 88 | photoImageView.frame = frameToCenter; 89 | } 90 | } 91 | 92 | // MARK: priviate 93 | 94 | private func setupView() { 95 | contentView.addSubview(zoomingScrollView) 96 | zoomingScrollView.addSubview(photoImageView) 97 | } 98 | 99 | private func displayImage() { 100 | zoomingScrollView.maximumZoomScale = 1 101 | zoomingScrollView.minimumZoomScale = 1; 102 | zoomingScrollView.zoomScale = 1 103 | zoomingScrollView.contentSize = CGSize(width: 0, height: 0); 104 | photoImageView.frame = zoomingScrollView.bounds 105 | requestID = DXPickerHelper.fetchImage(viaAsset:asset, 106 | targetSize: zoomingScrollView.dx_size, 107 | needHighQuality: true, 108 | imageResultHandler: { [unowned self](image) -> Void in 109 | self.requestID = nil 110 | guard image != nil else { 111 | return 112 | } 113 | self.photoImageView.image = image 114 | self.photoImageView.isHidden = false 115 | var photoImageViewFrame = CGRect.zero 116 | photoImageViewFrame.origin = CGPoint.zero 117 | photoImageViewFrame.size = image!.size; 118 | self.photoImageView.frame = photoImageViewFrame; 119 | self.zoomingScrollView.contentSize = photoImageViewFrame.size; 120 | // Set zoom to minimum zoom 121 | self.setMaxMinZoomScalesForCurrentBounds() 122 | self.setNeedsLayout() 123 | }) 124 | } 125 | 126 | private func setMaxMinZoomScalesForCurrentBounds() { 127 | // reset 128 | zoomingScrollView.maximumZoomScale = 1 129 | zoomingScrollView.minimumZoomScale = 1 130 | zoomingScrollView.zoomScale = 1 131 | guard photoImageView.image != nil else { return } 132 | // rest position 133 | photoImageView.frame = CGRect(x: 0, y: 0, width: photoImageView.dx_width, height: photoImageView.dx_height) 134 | // caculate the Min 135 | let boundSize = zoomingScrollView.bounds.size 136 | let imageSize = photoImageView.image!.size 137 | let xScale = boundSize.width / imageSize.width // the scale needed to perfectly fit the image width-wise 138 | let yScale = boundSize.height / imageSize.height // the scale needed to perfectly fit the image height-wise 139 | var minScale = min(xScale, yScale) // use minimum of these to allow the image to become fully visible 140 | // caculate Max 141 | var maxScale: CGFloat = 1.5 142 | if UI_USER_INTERFACE_IDIOM() == .pad { 143 | maxScale = 3 144 | } 145 | // Image is smaller than screen so no zooming! 146 | if (xScale >= 1 && yScale >= 1) { 147 | minScale = 1.0 148 | } 149 | // Set min/max zoom 150 | zoomingScrollView.maximumZoomScale = maxScale 151 | zoomingScrollView.minimumZoomScale = minScale 152 | // Initial zoom 153 | zoomingScrollView.zoomScale = initialZoomScaleWithMinScale() 154 | 155 | // If we're zooming to fill then centralise 156 | if (zoomingScrollView.zoomScale != minScale) { 157 | // Centralise 158 | zoomingScrollView.contentOffset = CGPoint(x: (imageSize.width * zoomingScrollView.zoomScale - boundSize.width) / 2.0, 159 | y: (imageSize.height * self.zoomingScrollView.zoomScale - boundSize.height) / 2.0) 160 | // Disable scrolling initially until the first pinch to fix issues with swiping on an initally zoomed in photo 161 | self.zoomingScrollView.isScrollEnabled = false 162 | } 163 | // Layout 164 | setNeedsLayout() 165 | } 166 | 167 | private func initialZoomScaleWithMinScale() -> CGFloat { 168 | var zoomScale = zoomingScrollView.minimumZoomScale 169 | // Zoom image to fill if the aspect ratios are fairly similar 170 | let boundsSize = zoomingScrollView.bounds.size 171 | let imageSize = photoImageView.image!.size 172 | let boundsAR = boundsSize.width / boundsSize.height 173 | let imageAR = imageSize.width / imageSize.height 174 | let xScale = boundsSize.width / imageSize.width // the scale needed to perfectly fit the image width-wise 175 | let yScale = boundsSize.height / imageSize.height // the scale needed to perfectly fit the image height-wise 176 | // Zooms standard portrait images on a 3.5in screen but not on a 4in screen. 177 | if (abs(boundsAR - imageAR) < 0.17) { 178 | zoomScale = max(xScale, yScale) 179 | // Ensure we don't zoom in or out too far, just in case 180 | zoomScale = min(max(self.zoomingScrollView.minimumZoomScale, zoomScale), self.zoomingScrollView.maximumZoomScale) 181 | } 182 | return zoomScale 183 | } 184 | 185 | // MARK: UIScrollViewDelegate 186 | 187 | func viewForZooming(in scrollView: UIScrollView) -> UIView? { 188 | return photoImageView 189 | } 190 | 191 | func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) { 192 | zoomingScrollView.isScrollEnabled = true 193 | } 194 | 195 | func scrollViewDidZoom(_ scrollView: UIScrollView) { 196 | self.setNeedsLayout() 197 | self.layoutIfNeeded() 198 | } 199 | 200 | // MARK: DXTapDetectingImageView 201 | 202 | func imageView(_ imageView: DXTapDetectingImageView?, singleTapDetected touch: UITouch?) { 203 | func handleSingleTap(touchPoint: CGPoint) { 204 | guard photoBrowser != nil else { 205 | return 206 | } 207 | photoBrowser?.perform(#selector(DXPhotoBrowser.toggleControls), with: nil, afterDelay: 0.2) 208 | } 209 | 210 | guard touch != nil else { 211 | DXLog("touch error") 212 | return 213 | } 214 | handleSingleTap(touchPoint: touch!.location(in: imageView)) 215 | } 216 | 217 | func imageView(_ imageView: DXTapDetectingImageView?, doubleTapDetected touch: UITouch?) { 218 | 219 | func handleDoubleTap(touchPoint: CGPoint) { 220 | guard photoBrowser != nil else { 221 | return 222 | } 223 | NSObject.cancelPreviousPerformRequests(withTarget: photoBrowser!) 224 | if (zoomingScrollView.zoomScale != zoomingScrollView.minimumZoomScale && zoomingScrollView.zoomScale != initialZoomScaleWithMinScale()) { 225 | zoomingScrollView.setZoomScale(zoomingScrollView.minimumZoomScale, animated: true) 226 | } else { 227 | let newZoomScale = (zoomingScrollView.maximumZoomScale + zoomingScrollView.minimumZoomScale) / 2 228 | let xsize = zoomingScrollView.dx_width / newZoomScale 229 | let ysize = zoomingScrollView.dx_height / newZoomScale 230 | zoomingScrollView.zoom(to: CGRect(x: touchPoint.x - xsize/2, y: touchPoint.y - ysize/2, width: xsize, height: ysize), animated: true) 231 | } 232 | } 233 | guard touch != nil else { 234 | DXLog("touch error") 235 | return 236 | } 237 | handleDoubleTap(touchPoint: touch!.location(in: imageView)) 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Views/DXFullImageButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXFullImageButton.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by DingXiao on 15/10/30. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DXFullImageButton: UIView { 12 | 13 | struct DXFullImageButtonParam { 14 | static let buttonPadding: CGFloat = 20 15 | static let buttonImageWidth: CGFloat = 16 16 | static let buttonFont = UIFont.systemFont(ofSize: 13) 17 | } 18 | 19 | // MARK: lazy load property 20 | 21 | private lazy var fullImageButton: UIButton = { 22 | let button = UIButton(type: .custom) 23 | button.translatesAutoresizingMaskIntoConstraints = false 24 | button.backgroundColor = self.backgroundColor 25 | self.addSubview(button) 26 | return button 27 | }() 28 | 29 | private lazy var imageView: UIImageView = { 30 | let imv = UIImageView(frame: CGRect.zero) 31 | imv.translatesAutoresizingMaskIntoConstraints = false 32 | imv.backgroundColor = self.backgroundColor 33 | return imv 34 | }() 35 | 36 | private lazy var nameLabel: UILabel = { 37 | let label = UILabel(frame: CGRect.zero) 38 | label.text = DXlocalized(string: "fullImage", comment: "原图") 39 | label.translatesAutoresizingMaskIntoConstraints = false 40 | label.backgroundColor = self.backgroundColor 41 | label.font = UIFont.systemFont(ofSize: 14) 42 | label.textAlignment = .left 43 | label.textColor = UIColor.white 44 | return label 45 | }() 46 | 47 | private lazy var imageSizeLabel: UILabel = { 48 | let label = UILabel(frame: CGRect.zero) 49 | label.translatesAutoresizingMaskIntoConstraints = false 50 | label.backgroundColor = self.backgroundColor 51 | label.font = UIFont.systemFont(ofSize: 13) 52 | label.textAlignment = .left 53 | label.textColor = UIColor.white 54 | return label 55 | }() 56 | 57 | private lazy var indicatorView: UIActivityIndicatorView = { 58 | let view = UIActivityIndicatorView(frame: CGRect.zero) 59 | view.translatesAutoresizingMaskIntoConstraints = false 60 | view.hidesWhenStopped = true 61 | view.stopAnimating() 62 | return view 63 | }() 64 | 65 | // MARK: public 66 | var text: String { 67 | didSet { 68 | self.imageSizeLabel.text = text 69 | } 70 | } 71 | 72 | var selected: Bool { 73 | didSet { 74 | self.imageSizeLabel.isHidden = !selected 75 | if selected { 76 | self.imageView.image = UIImage(named: "photo_full_image_selected") 77 | } else { 78 | self.imageView.image = UIImage(named: "photo_full_image_unselected") 79 | } 80 | } 81 | } 82 | 83 | func addTarget(target: AnyObject?, action: Selector) { 84 | self.fullImageButton.addTarget(target, action: action, for: UIControlEvents.touchUpInside) 85 | } 86 | 87 | func shouldAnimating(animated: Bool) { 88 | if selected { 89 | self.imageSizeLabel.isHidden = animated 90 | if animated { 91 | self.indicatorView.startAnimating() 92 | } else { 93 | self.indicatorView.stopAnimating() 94 | } 95 | } 96 | } 97 | 98 | // MARK: priviate 99 | 100 | private func setupView() { 101 | self.backgroundColor = UIColor.clear 102 | self.addSubview(fullImageButton) 103 | self.addSubview(imageView) 104 | self.addSubview(nameLabel) 105 | self.addSubview(imageSizeLabel) 106 | self.addSubview(indicatorView) 107 | self.imageView.image = UIImage(named: "photo_full_image_unselected") 108 | let viewBindingsDict = [ 109 | "fullImageButton":fullImageButton, 110 | "imageView": imageView, 111 | "nameLabel": nameLabel, 112 | "imageSizeLabel": imageSizeLabel, 113 | "indicatorView": indicatorView 114 | ] as [String : Any] 115 | 116 | let btVflH = "H:|-0-[fullImageButton]-0-|" 117 | let btVflV = "V:|-0-[fullImageButton]-0-|" 118 | let vflH = "H:|-0-[imageView(20)]-5-[nameLabel(>=2)]-5-[imageSizeLabel(80)]" 119 | let constraintsVflH = NSLayoutConstraint.constraints(withVisualFormat: vflH, options: .alignAllCenterY, metrics: nil, views: viewBindingsDict) 120 | let btContstraintsH = NSLayoutConstraint.constraints(withVisualFormat: btVflH, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewBindingsDict) 121 | let btContstraintsV = NSLayoutConstraint.constraints(withVisualFormat: btVflV, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewBindingsDict) 122 | let imageCenter = NSLayoutConstraint(item: imageView, attribute: .centerY, relatedBy: .equal, toItem: fullImageButton, attribute: .centerY, multiplier: 1, constant: 0) 123 | let imageViewHeight = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: imageView, attribute: .width, multiplier: 1, constant: 0) 124 | let nameLabelHeight = NSLayoutConstraint(item: nameLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: 30) 125 | let imageSizeLabelHeight = NSLayoutConstraint(item: imageSizeLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: 30) 126 | let indicatorViewWidth = NSLayoutConstraint(item: indicatorView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 26) 127 | let indicatorViewLeading = NSLayoutConstraint(item: indicatorView, attribute: .leading, relatedBy: .equal, toItem: imageSizeLabel, attribute: .leading, multiplier: 1, constant: 0) 128 | let indicatorViewCenterY = NSLayoutConstraint(item: indicatorView, attribute: .centerY, relatedBy: .equal, toItem: imageSizeLabel, attribute: .centerY, multiplier: 1, constant: 0) 129 | let indicatorViewHeight = NSLayoutConstraint(item: indicatorView, attribute: .height, relatedBy: .equal, toItem: indicatorView, attribute: .width, multiplier: 1, constant: 0) 130 | 131 | 132 | self.addConstraints(btContstraintsH) 133 | self.addConstraints(btContstraintsV) 134 | self.addConstraints(constraintsVflH) 135 | self.addConstraints([imageCenter, imageViewHeight, nameLabelHeight, imageSizeLabelHeight,indicatorViewCenterY,indicatorViewLeading,indicatorViewWidth,indicatorViewHeight]) 136 | } 137 | 138 | // MARK: init 139 | override init(frame: CGRect) { 140 | self.selected = false 141 | self.text = "" 142 | super.init(frame: frame) 143 | self.setupView() 144 | } 145 | 146 | required init?(coder aDecoder: NSCoder) { 147 | self.selected = false 148 | self.text = "" 149 | super.init(coder: aDecoder) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Views/DXPromptView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXPromptView.swift 3 | // DXPhotosPickerDemo 4 | // 5 | // Created by DingXiao on 15/10/13. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class DXPromptView: UIWindow { 12 | 13 | convenience init(imageName: String?, message: String?) { 14 | self.init() 15 | self.isHidden = false 16 | self.alpha = 1.0 17 | self.windowLevel = UIWindowLevelStatusBar + 1.0 18 | self.backgroundColor = UIColor(red: 0x17/255.0, green: 0x17/255.0, blue: 0x17/255.0, alpha: 0.9) 19 | self.layer.masksToBounds = true 20 | let image = UIImage(named: imageName!) 21 | let imageView = UIImageView(image: image) 22 | self.addSubview(imageView) 23 | 24 | let label = UILabel() 25 | label.font = UIFont.systemFont(ofSize: 14.0) 26 | label.backgroundColor = UIColor.clear 27 | label.textColor = UIColor.white 28 | label.textAlignment = .center 29 | label.numberOfLines = 0 30 | label.text = message 31 | label.sizeToFit() 32 | self.addSubview(label) 33 | } 34 | 35 | public class func showWithImageName(imageName: String, message: String) { 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Views/DXSendButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXSendButton.swift 3 | // DXPhotosPickerDemo 4 | // 5 | // Created by Ding Xiao on 15/10/14. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DXSendButton: UIView { 12 | 13 | struct DXSendButtonConfig { 14 | static let sendButtonTintNormalColor = "#1FB823" 15 | static let sendButtonTintHighlightedColor = "#C9EFCA" 16 | static let sendButtonTintDisabledColor = "#C9EFCB" 17 | static let sendButtonTextWitdh: CGFloat = 38.0 18 | static let commonSize = CGSize(width: 20, height: 20) 19 | static let sendButtonFont = UIFont.systemFont(ofSize: 15.0) 20 | } 21 | 22 | var badgeValue: String = "0" { 23 | didSet { 24 | 25 | func showBadgeValue() { 26 | self.badgeValueLabel.isHidden = false 27 | self.backGroudView.isHidden = false 28 | self.sendButton.isEnabled = true 29 | } 30 | 31 | func hideBadgeValue() { 32 | self.badgeValueLabel.isHidden = true 33 | self.backGroudView.isHidden = true 34 | self.sendButton.isEnabled = false 35 | } 36 | 37 | let str = badgeValue as NSString 38 | let rect = str.boundingRect(with: CGSize(width: 100, height: 20), 39 | options: .truncatesLastVisibleLine, 40 | attributes: [NSFontAttributeName:DXSendButtonConfig.sendButtonFont], 41 | context: nil) 42 | self.badgeValueLabel.dx_width = (rect.size.width + 9) > 20 ? (rect.size.width + 9) : 20 43 | self.badgeValueLabel.dx_height = 20 44 | self.sendButton.dx_width = self.badgeValueLabel.dx_width + DXSendButtonConfig.sendButtonTextWitdh 45 | self.dx_width = self.sendButton.dx_width 46 | self.badgeValueLabel.text = badgeValue; 47 | 48 | if str.integerValue > 0 { 49 | showBadgeValue() 50 | self.backGroudView.transform = CGAffineTransform(scaleX: 0, y: 0); 51 | UIView.animate(withDuration: 0.2, animations: { [unowned self]() -> Void in 52 | self.backGroudView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1) 53 | }, completion: { [unowned self] (completed) -> Void in 54 | self.backGroudView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0) 55 | }) 56 | } else { 57 | hideBadgeValue() 58 | } 59 | } 60 | } 61 | 62 | override init(frame: CGRect) { 63 | super.init(frame: frame) 64 | self.frame = CGRect(x: 0, y: 0, width: 56, height: 26) 65 | setupViews() 66 | } 67 | 68 | required init?(coder aDecoder: NSCoder) { 69 | super.init(coder: aDecoder) 70 | setupViews() 71 | } 72 | 73 | private func setupViews() { 74 | addSubview(backGroudView) 75 | addSubview(badgeValueLabel) 76 | addSubview(sendButton) 77 | self.badgeValue = "0" 78 | } 79 | 80 | // MARK: public 81 | 82 | func addTarget(target: AnyObject?, action: Selector) { 83 | self.sendButton.addTarget(target, action: action, for: UIControlEvents.touchUpInside) 84 | } 85 | 86 | // MARK: lazy load 87 | 88 | private lazy var badgeValueLabel: UILabel = { 89 | let label = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) 90 | label.dx_centerY = self.dx_centerY 91 | label.backgroundColor = UIColor.clear 92 | label.textColor = UIColor.white 93 | label.font = DXSendButtonConfig.sendButtonFont 94 | label.textAlignment = NSTextAlignment.center 95 | return label 96 | }() 97 | 98 | private lazy var backGroudView: UIView = { 99 | let view = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) 100 | view.dx_centerY = self.dx_centerY 101 | view.backgroundColor = UIColor(hexColor: DXSendButtonConfig.sendButtonTintNormalColor) 102 | view.layer.cornerRadius = DXSendButtonConfig.commonSize.width/2 103 | return view 104 | }() 105 | 106 | private lazy var sendButton: UIButton = { 107 | let button = UIButton(type: UIButtonType.custom) 108 | button.frame = self.bounds 109 | button.setTitle(DXlocalized(string: "send", comment: "发送"), for: UIControlState.normal) 110 | button.setTitleColor(UIColor(hexColor: DXSendButtonConfig.sendButtonTintNormalColor), for: UIControlState.normal) 111 | button.setTitleColor(UIColor(hexColor: DXSendButtonConfig.sendButtonTintHighlightedColor), for: UIControlState.highlighted) 112 | button.setTitleColor(UIColor(hexColor: DXSendButtonConfig.sendButtonTintDisabledColor), for: UIControlState.disabled) 113 | button.titleLabel?.font = DXSendButtonConfig.sendButtonFont 114 | button.contentEdgeInsets = UIEdgeInsetsMake(0, 20, 0, 0) 115 | button.backgroundColor = UIColor.clear 116 | return button 117 | }() 118 | } 119 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Views/DXTapDetectingImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXTapDetectingImageView.swift 3 | // DXPhotosPickerDemo 4 | // Inspired by MWTapDetectingImageView github:https://github.com/mwaterfall/MWPhotoBrowser 5 | // Created by Ding Xiao on 15/10/14. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | // A swift version of MWTapDetectingImageView 9 | 10 | import UIKit 11 | 12 | @objc protocol DXTapDetectingImageViewDelegate: NSObjectProtocol { 13 | @objc optional func imageView(_: DXTapDetectingImageView?, singleTapDetected touch: UITouch?) 14 | @objc optional func imageView(_: DXTapDetectingImageView?, doubleTapDetected touch: UITouch?) 15 | @objc optional func imageView(_: DXTapDetectingImageView?, tripleTapDetected touch: UITouch?) 16 | } 17 | 18 | class DXTapDetectingImageView: UIImageView, DXTapDetectingImageViewDelegate { 19 | 20 | weak var tapDelegate: DXTapDetectingImageViewDelegate? 21 | 22 | // MARK: initialize 23 | 24 | override init(frame: CGRect) { 25 | super.init(frame: frame) 26 | self.isUserInteractionEnabled = true 27 | } 28 | 29 | override init(image: UIImage?) { 30 | super.init(image: image) 31 | self.isUserInteractionEnabled = true 32 | } 33 | 34 | override init(image: UIImage?, highlightedImage: UIImage?) { 35 | super.init(image: image, highlightedImage: highlightedImage) 36 | self.isUserInteractionEnabled = true 37 | } 38 | 39 | required init?(coder aDecoder: NSCoder) { 40 | fatalError("init(coder:) has not been implemented") 41 | } 42 | 43 | // MARK: touch events 44 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 45 | func handleSingleTap(touch: UITouch?) { 46 | self.tapDelegate?.imageView?(self, singleTapDetected: touch) 47 | } 48 | 49 | func handleDoubleTap(touch: UITouch?) { 50 | tapDelegate?.imageView?(self, doubleTapDetected: touch) 51 | } 52 | 53 | func handleTripleTap(touch: UITouch?) { 54 | tapDelegate?.imageView?(self, tripleTapDetected: touch) 55 | } 56 | 57 | let touch = touches.first 58 | let tapcount = touch?.tapCount 59 | switch(tapcount!) { 60 | case 1: handleSingleTap(touch: touch) 61 | case 2: handleDoubleTap(touch: touch) 62 | case 3: handleTripleTap(touch: touch) 63 | default: break 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /DXPhotoPicker/Classes/Views/DXUnAuthorizedTipsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXUnAuthorizedTipsView.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by DingXiao on 15/10/16. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DXUnAuthorizedTipsView: UIView { 12 | 13 | lazy var imageView: UIImageView! = { 14 | let imv = UIImageView(image: UIImage(named: "image_unAuthorized")) 15 | imv.translatesAutoresizingMaskIntoConstraints = false 16 | return imv 17 | }() 18 | 19 | lazy var label: UILabel! = { 20 | let lb = UILabel(frame: CGRect.zero) 21 | let text = DXlocalized(string: "UnAuthorizedTip", comment: "UnAuthorizedTip") as NSString 22 | let infoDic = Bundle.main.infoDictionary 23 | var displayName = infoDic!["CFBundleDisplayName"] as? NSString 24 | if displayName == nil { 25 | displayName = infoDic!["CFBundleName"] as? NSString 26 | if displayName == nil { 27 | displayName = "" 28 | } 29 | } 30 | let tipString = NSString(format: text, displayName!) 31 | lb.text = tipString as String 32 | lb.textColor = UIColor.black 33 | lb.font = UIFont.systemFont(ofSize: 14.0) 34 | lb.textAlignment = NSTextAlignment.center 35 | lb.numberOfLines = 0 36 | lb.backgroundColor = UIColor.clear 37 | lb.lineBreakMode = NSLineBreakMode.byTruncatingTail 38 | return lb 39 | }() 40 | 41 | override init(frame: CGRect) { 42 | super.init(frame: frame) 43 | self.setup() 44 | } 45 | 46 | required init?(coder aDecoder: NSCoder) { 47 | super.init(coder: aDecoder) 48 | self.setup() 49 | } 50 | 51 | private func setup() { 52 | addSubview(imageView) 53 | addSubview(label) 54 | imageView.translatesAutoresizingMaskIntoConstraints = false 55 | label.translatesAutoresizingMaskIntoConstraints = false 56 | let viewBindingsDict = [ 57 | "label": label, 58 | "imageView": imageView 59 | ] as [String : Any] 60 | let mertic = [ 61 | "imageLength": 130, 62 | "labelHeight": 60 63 | ] 64 | let vflV = "V:|-120-[imageView(imageLength)]-30-[label(<=labelHeight@750)]" 65 | let vflH = "H:|-33-[label]-33-|" 66 | let contstraintsV: Array = NSLayoutConstraint.constraints(withVisualFormat: vflV, 67 | options: NSLayoutFormatOptions.alignAllCenterX, 68 | metrics: mertic, 69 | views: viewBindingsDict) 70 | let contstraintsH: Array = NSLayoutConstraint.constraints(withVisualFormat: vflH, 71 | options: NSLayoutFormatOptions(rawValue: 0), 72 | metrics: mertic, 73 | views: viewBindingsDict) 74 | let imageViewConttraintsWidth = NSLayoutConstraint(item: imageView, 75 | attribute: NSLayoutAttribute.width, 76 | relatedBy: NSLayoutRelation.equal, 77 | toItem: nil, 78 | attribute: NSLayoutAttribute.notAnAttribute, 79 | multiplier: 0, 80 | constant: 130.0) 81 | self.addConstraints(contstraintsV) 82 | self.addConstraints(contstraintsH) 83 | self.addConstraint(imageViewConttraintsWidth) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/en.lproj/DXPhotoPicker.strings: -------------------------------------------------------------------------------- 1 | /* 2 | DXPhotosPicker.strings 3 | DXPhotosPickerDemo 4 | 5 | Created by DingXiao on 15/10/13. 6 | Copyright © 2015年 Dennis. All rights reserved. 7 | */ 8 | 9 | "albumTitle" = "Photos"; 10 | "cancel" = "Cancel"; 11 | "send" = "send"; 12 | "preview" = "preview"; 13 | "fullImage" = "Full Image"; 14 | "alertTitle" = "Tips"; 15 | "alertContent" = "Can not select more than %@ photos"; 16 | "alertButton" = "OK"; 17 | "UnAuthorizedTip" = "Allow %@ to access your album in \"Settings\"->\"Privacy\"->\"Photos\""; -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/assets_placeholder_picture.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "assets_placeholder_picture@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/assets_placeholder_picture.imageset/assets_placeholder_picture@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPicker/Resources/imageResources.xcassets/images/assets_placeholder_picture.imageset/assets_placeholder_picture@2x.png -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/back_highlight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "back_highlight.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/back_highlight.imageset/back_highlight.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPicker/Resources/imageResources.xcassets/images/back_highlight.imageset/back_highlight.pdf -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/back_normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "back_normal.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/back_normal.imageset/back_normal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPicker/Resources/imageResources.xcassets/images/back_normal.imageset/back_normal.pdf -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/compose_photograph_background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "compose_photograph_background@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/compose_photograph_background.imageset/compose_photograph_background@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPicker/Resources/imageResources.xcassets/images/compose_photograph_background.imageset/compose_photograph_background@2x.png -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/image_unAuthorized.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "image_unAuthorized.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/image_unAuthorized.imageset/image_unAuthorized.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPicker/Resources/imageResources.xcassets/images/image_unAuthorized.imageset/image_unAuthorized.pdf -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/photo_check_default.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "photo_check_default.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/photo_check_default.imageset/photo_check_default.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPicker/Resources/imageResources.xcassets/images/photo_check_default.imageset/photo_check_default.pdf -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/photo_check_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "photo_check_selected.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/photo_check_selected.imageset/photo_check_selected.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPicker/Resources/imageResources.xcassets/images/photo_check_selected.imageset/photo_check_selected.pdf -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/photo_full_image_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "photo_full_image_selected.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/photo_full_image_selected.imageset/photo_full_image_selected.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPicker/Resources/imageResources.xcassets/images/photo_full_image_selected.imageset/photo_full_image_selected.pdf -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/photo_full_image_unselected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "photo_full_image_unselected.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/photo_full_image_unselected.imageset/photo_full_image_unselected.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPicker/Resources/imageResources.xcassets/images/photo_full_image_unselected.imageset/photo_full_image_unselected.pdf -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/picker_alert_sigh.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "picker_alert_sigh@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/picker_alert_sigh.imageset/picker_alert_sigh@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPicker/Resources/imageResources.xcassets/images/picker_alert_sigh.imageset/picker_alert_sigh@2x.png -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/picker_image_placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "picker_image_placeholder@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/imageResources.xcassets/images/picker_image_placeholder.imageset/picker_image_placeholder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPicker/Resources/imageResources.xcassets/images/picker_image_placeholder.imageset/picker_image_placeholder@2x.png -------------------------------------------------------------------------------- /DXPhotoPicker/Resources/zh-Hans.lproj/DXPhotoPicker.strings: -------------------------------------------------------------------------------- 1 | /* 2 | DXPhotosPicker.strings 3 | DXPhotosPickerDemo 4 | 5 | Created by DingXiao on 15/10/13. 6 | Copyright © 2015年 Dennis. All rights reserved. 7 | */ 8 | 9 | "albumTitle" = "照片"; 10 | "cancel" = "取消"; 11 | "send" = "发送"; 12 | "preview" = "预览"; 13 | "fullImage" = "原图"; 14 | "alertTitle" = "提示"; 15 | "alertContent" = "不能超过%@张图片"; 16 | "alertButton" = "确定"; 17 | "UnAuthorizedTip" = "请在iPhone的\"设置-隐私-照片\"选项中,\n允许%@访问你的手机相册"; -------------------------------------------------------------------------------- /DXPhotoPicker/Support/DXPickerHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXPickerManager.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by Ding Xiao on 15/10/19. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | private let kDXPickerManagerDefaultAlbumIdentifier = "com.dennis.kDXPhotoPickerStoredGroup" 13 | 14 | class DXPickerHelper: NSObject { 15 | 16 | class func save(identifier: String?) { 17 | guard identifier != nil else { 18 | return 19 | } 20 | UserDefaults.standard.set(identifier!, forKey: kDXPickerManagerDefaultAlbumIdentifier) 21 | UserDefaults.standard.synchronize() 22 | } 23 | 24 | class func fetchAlbumIdentifier() -> String? { 25 | let string = UserDefaults.standard.object(forKey: kDXPickerManagerDefaultAlbumIdentifier) as? String 26 | return string 27 | } 28 | 29 | class func fetchAlbum() -> DXAlbum { 30 | let album = DXAlbum() 31 | let identifier = fetchAlbumIdentifier() 32 | guard identifier != nil else { 33 | return album 34 | } 35 | let options = PHFetchOptions() 36 | options.predicate = NSPredicate( 37 | format: "mediaType = %d", PHAssetMediaType.image.rawValue 38 | ) 39 | options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)] 40 | let result = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [identifier!], options: nil) 41 | if result.count <= 0 { 42 | return album 43 | } 44 | 45 | let collection = result.firstObject 46 | let requestResult = PHAsset.fetchAssets(in: collection!, options: options) as? PHFetchResult 47 | album.name = collection?.localizedTitle 48 | album.results = requestResult 49 | if let count = requestResult?.count { 50 | album.count = count 51 | } 52 | album.startDate = collection?.startDate 53 | album.identifier = collection?.localIdentifier 54 | return album 55 | } 56 | 57 | class func fetchAlbumList() -> [DXAlbum]? { 58 | 59 | func fetchAlbums() -> [PHFetchResult]? { 60 | let userAlbumsOptions = PHFetchOptions() 61 | userAlbumsOptions.predicate = NSPredicate(format: "estimatedAssetCount > 0") 62 | userAlbumsOptions.sortDescriptors = [NSSortDescriptor(key: "startDate", ascending: false)] 63 | var albums = [PHFetchResult]() 64 | albums.append( 65 | PHAssetCollection.fetchAssetCollections( 66 | with: PHAssetCollectionType.smartAlbum, 67 | subtype: PHAssetCollectionSubtype.albumRegular, 68 | options: nil) 69 | ) 70 | albums.append( 71 | PHAssetCollection.fetchAssetCollections( 72 | with: PHAssetCollectionType.album, 73 | subtype: PHAssetCollectionSubtype.any, 74 | options: userAlbumsOptions) 75 | ) 76 | return albums 77 | } 78 | 79 | let results = fetchAlbums() 80 | var list: [DXAlbum] = [] 81 | guard results != nil else { 82 | return nil 83 | } 84 | let options = PHFetchOptions() 85 | options.predicate = NSPredicate( 86 | format: "mediaType = %d", PHAssetMediaType.image.rawValue 87 | ) 88 | options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)] 89 | for (_, result) in results!.enumerated() { 90 | result.enumerateObjects({ (collection, index, isStop) -> Void in 91 | let album = collection 92 | let assetResults = PHAsset.fetchAssets(in: album, options: options) 93 | var count = 0 94 | switch album.assetCollectionType { 95 | case .album: 96 | count = assetResults.count 97 | case .smartAlbum: 98 | count = assetResults.count 99 | case .moment: 100 | count = 0 101 | } 102 | if count > 0 { 103 | autoreleasepool { 104 | let ab = DXAlbum() 105 | ab.count = count 106 | ab.results = assetResults as? PHFetchResult 107 | ab.name = album.localizedTitle 108 | ab.startDate = album.startDate 109 | ab.identifier = album.localIdentifier 110 | list.append(ab) 111 | } 112 | } 113 | }) 114 | } 115 | return list 116 | } 117 | 118 | /** 119 | Fetch the image with the default mode AspectFill 120 | 'call the method fetchImageWithAsset: targetSize: contentMode: imageResultHandler: 121 | 'the mode is AspectFill 122 | 123 | - parameter asset: the asset you want to be requested 124 | - parameter targetSize: the size customed 125 | - parameter imageResultHandler: image result 126 | @image the parameter image in block is the requested image 127 | 128 | - returns: PHImageRequestID so that you can cancel the request if needed 129 | */ 130 | @discardableResult 131 | class func fetchImage(viaAsset asset: PHAsset?, targetSize: CGSize, imageResultHandler: @escaping (_ image: UIImage?)->Void) -> PHImageRequestID? { 132 | guard asset != nil else { 133 | return nil 134 | } 135 | let options = PHImageRequestOptions() 136 | options.resizeMode = PHImageRequestOptionsResizeMode.exact 137 | let scale = UIScreen.main.scale 138 | let size = CGSize(width: targetSize.width * scale, height: targetSize.height * scale) 139 | return PHCachingImageManager.default().requestImage(for: asset!, 140 | targetSize: size, 141 | contentMode: .aspectFill, 142 | options: options) { 143 | (result, info) -> Void in 144 | imageResultHandler(result) 145 | } 146 | } 147 | @discardableResult 148 | class func fetchImage(viaAsset asset: PHAsset?, targetSize: CGSize, needHighQuality: Bool,imageResultHandler: @escaping (_ image: UIImage?)->Void) -> PHImageRequestID? { 149 | guard asset != nil else { 150 | return nil 151 | } 152 | let options = PHImageRequestOptions() 153 | if needHighQuality { 154 | options.deliveryMode = .highQualityFormat 155 | } else { 156 | options.resizeMode = .exact 157 | } 158 | 159 | let scale = UIScreen.main.scale 160 | let size = CGSize(width: targetSize.width*scale, height: targetSize.height*scale); 161 | return PHImageManager.default().requestImage(for: asset!, 162 | targetSize: size, 163 | contentMode: .aspectFit, 164 | options: options) { 165 | (result, info) -> Void in 166 | imageResultHandler(result) 167 | } 168 | } 169 | 170 | class func fetchImageSize( 171 | asset: PHAsset?, 172 | imageSizeResultHandler: @escaping ((_ imageSize: Float, _ sizeString: String) -> Void)) { 173 | guard asset != nil else { 174 | return 175 | } 176 | PHImageManager.default().requestImageData(for: asset!, 177 | options: nil, 178 | resultHandler: {(data, string, orientation, obj) -> Void in 179 | var string = "0M" 180 | var imageSize: Float = 0.0 181 | guard data != nil else { 182 | imageSizeResultHandler(imageSize, string) 183 | return 184 | } 185 | imageSize = Float(data!.count) 186 | if imageSize > 1024*1024 { 187 | let size: Float = imageSize/(1024*1024) 188 | string = "\(size.format("0.1"))" + "M" 189 | imageSizeResultHandler(imageSize, string) 190 | } else { 191 | let size: Float = imageSize/1024 192 | string = "\(size.format("0.1"))" + "K" 193 | imageSizeResultHandler(imageSize, string) 194 | } 195 | }) 196 | } 197 | 198 | class func fetchImageAssets(inCollectionResults results: PHFetchResult?) -> [PHAsset]{ 199 | var resutsArray : Array = [] 200 | guard results != nil else { 201 | return resutsArray 202 | } 203 | results?.enumerateObjects({ (asset, index, isStop) -> Void in 204 | resutsArray.append(asset as! PHAsset) 205 | }) 206 | return resutsArray 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /DXPhotoPicker/Support/DXUtil.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXUtil.swift 3 | // DXPhotosPickerDemo 4 | // 5 | // Created by Ding Xiao on 15/10/15. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public func DXLog(_ message: T, file: String = #file, method: String = #function,line: Int = #line) { 12 | #if DEBUG 13 | print("\((file as NSString).lastPathComponent)[line:\(line)], \(method): \(message)") 14 | #endif 15 | } 16 | 17 | public func DXlocalized(string: String, comment: String) -> String { 18 | return NSLocalizedString(string, tableName: "DXPhotoPicker", bundle: Bundle.main, value: "", comment: comment) 19 | } 20 | 21 | 22 | extension Float { 23 | func format(_ f: String) -> String { 24 | return NSString(format: "%0.1f", self) as String 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /DXPhotoPicker/Support/UICollectionView+Convenience.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+Convenience.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by DingXiao on 15/12/30. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UICollectionView { 12 | func aapl_indexPathsForElementsInRect(rect: CGRect) -> [NSIndexPath]? { 13 | let allLayoutAttributes = collectionViewLayout.layoutAttributesForElements(in: rect) 14 | if allLayoutAttributes?.count == 0 { 15 | return nil 16 | } 17 | var indexPaths = [NSIndexPath]() 18 | for (_, layoutAttributes) in allLayoutAttributes!.enumerated() { 19 | let indexPath = layoutAttributes.indexPath; 20 | indexPaths.append(indexPath as NSIndexPath) 21 | } 22 | return indexPaths 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /DXPhotoPicker/Support/UIColor+DXHex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+DXHex.swift 3 | // DXPhotosPickerDemo 4 | // 5 | // Created by Ding Xiao on 15/10/13. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | 12 | extension UIColor { 13 | convenience init(hexColor: String) { 14 | var red: CGFloat = 0.0 15 | var green: CGFloat = 0.0 16 | var blue: CGFloat = 0.0 17 | var alpha: CGFloat = 1.0 18 | 19 | var hexColorString = hexColor; 20 | 21 | if !hexColorString.hasPrefix("#"){ 22 | hexColorString = "#"+hexColor; 23 | } 24 | 25 | let index = hexColorString.index(hexColorString.startIndex, offsetBy: 1); 26 | let hex = hexColorString.substring(from: index) 27 | let scanner = Scanner(string: hex) 28 | var hexValue: CUnsignedLongLong = 0 29 | if scanner.scanHexInt64(&hexValue) { 30 | switch (hex.characters.count) { 31 | case 3: 32 | red = CGFloat((hexValue & 0xF00) >> 8) / 15.0 33 | green = CGFloat((hexValue & 0x0F0) >> 4) / 15.0 34 | blue = CGFloat(hexValue & 0x00F) / 15.0 35 | case 4: 36 | red = CGFloat((hexValue & 0xF000) >> 12) / 15.0 37 | green = CGFloat((hexValue & 0x0F00) >> 8) / 15.0 38 | blue = CGFloat((hexValue & 0x00F0) >> 4) / 15.0 39 | alpha = CGFloat(hexValue & 0x000F) / 15.0 40 | case 6: 41 | red = CGFloat((hexValue & 0xFF0000) >> 16) / 255.0 42 | green = CGFloat((hexValue & 0x00FF00) >> 8) / 255.0 43 | blue = CGFloat(hexValue & 0x0000FF) / 255.0 44 | case 8: 45 | red = CGFloat((hexValue & 0xFF000000) >> 24) / 255.0 46 | green = CGFloat((hexValue & 0x00FF0000) >> 16) / 255.0 47 | blue = CGFloat((hexValue & 0x0000FF00) >> 8) / 255.0 48 | alpha = CGFloat(hexValue & 0x000000FF) / 255.0 49 | default: 50 | DXLog("Invalid RGB string, number of characters after '#' should be either 3, 4, 6 or 8") 51 | } 52 | } else { 53 | DXLog("Scan hex error") 54 | } 55 | self.init(red:red, green:green, blue:blue, alpha:alpha) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /DXPhotoPicker/Support/UIView+DXFrame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+DXPhotoPicker.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by DingXiao on 15/10/16. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | 13 | var dx_left: CGFloat { 14 | set { 15 | var frame = self.frame 16 | frame.origin.x = newValue 17 | self.frame = frame 18 | } 19 | get { 20 | return self.frame.origin.x 21 | } 22 | } 23 | 24 | var dx_top: CGFloat { 25 | set { 26 | var frame = self.frame 27 | frame.origin.y = newValue; 28 | self.frame = frame 29 | } 30 | get { 31 | return self.frame.origin.y 32 | } 33 | } 34 | 35 | var dx_right: CGFloat { 36 | get { 37 | return self.frame.origin.x + self.frame.size.width 38 | } 39 | set { 40 | var frame = self.frame 41 | frame.origin.x = newValue - self.frame.width 42 | self.frame = frame 43 | } 44 | } 45 | 46 | var dx_bottom: CGFloat { 47 | get { 48 | return self.frame.origin.y + self.frame.size.height 49 | } 50 | set { 51 | var frame = self.frame 52 | frame.origin.y = newValue - frame.size.height 53 | self.frame = frame 54 | } 55 | } 56 | 57 | var dx_centerX: CGFloat { 58 | set { 59 | self.center = CGPoint(x: newValue, y: self.center.y) 60 | } 61 | get { 62 | return self.center.x 63 | } 64 | } 65 | 66 | var dx_centerY: CGFloat { 67 | set { 68 | self.center = CGPoint(x: self.center.x, y: newValue) 69 | } 70 | get { 71 | return self.center.y 72 | } 73 | } 74 | 75 | var dx_width: CGFloat { 76 | set { 77 | var frame = self.frame 78 | frame.size.width = newValue 79 | self.frame = frame 80 | } 81 | get { 82 | return self.frame.size.width 83 | } 84 | } 85 | 86 | var dx_height: CGFloat { 87 | set { 88 | var frame = self.frame 89 | frame.size.height = newValue 90 | self.frame = frame 91 | } 92 | get { 93 | return self.frame.size.height 94 | } 95 | } 96 | 97 | var dx_origin: CGPoint { 98 | get { 99 | return self.frame.origin 100 | } 101 | set { 102 | var frame = self.frame 103 | frame.origin = newValue 104 | self.frame = frame 105 | } 106 | } 107 | 108 | var dx_size: CGSize { 109 | get { 110 | return self.frame.size 111 | } 112 | set { 113 | var frame = self.frame 114 | frame.size = newValue 115 | self.frame = frame 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /DXPhotoPicker/Support/UIViewController+DXPhotoPicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+DXPhotoPicker.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by DingXiao on 15/10/22. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public enum DXPhotoPickerNavigationBarPosition: Int { 12 | case left 13 | case right 14 | } 15 | 16 | public extension UIViewController { 17 | 18 | public func createBarButtonItemAt( 19 | position: DXPhotoPickerNavigationBarPosition, 20 | normalImage: UIImage?, 21 | highlightImage: UIImage?, 22 | action: Selector) { 23 | let button = UIButton(type: .custom) 24 | var insets = UIEdgeInsets.zero 25 | switch position { 26 | case .left : 27 | insets = UIEdgeInsetsMake(0, -20, 0, 20) 28 | case .right : 29 | insets = UIEdgeInsetsMake(0, 13, 0, -13) 30 | } 31 | button.imageEdgeInsets = insets 32 | button.addTarget(self, action: action, for: .touchUpInside) 33 | button.frame = CGRect(x: 0, y: 0, width: 44, height: 44) 34 | button.setImage(normalImage, for: .normal) 35 | button.setImage(highlightImage, for: .highlighted) 36 | 37 | let barButtonItem = UIBarButtonItem(customView: button) 38 | switch position { 39 | case .left: 40 | self.navigationItem.leftBarButtonItem = barButtonItem 41 | case .right: 42 | self.navigationItem.rightBarButtonItem = barButtonItem 43 | } 44 | } 45 | 46 | public func createBarButtonItemAt( 47 | position: DXPhotoPickerNavigationBarPosition, 48 | text: String, 49 | action: Selector 50 | ) { 51 | let button = UIButton(type: .custom) 52 | var insets = UIEdgeInsets.zero 53 | switch position { 54 | case .left : 55 | insets = UIEdgeInsetsMake(0, -49+26, 0, 19) 56 | case .right : 57 | insets = UIEdgeInsetsMake(0, 49-26, 0, -19) 58 | } 59 | button.imageEdgeInsets = insets 60 | button.addTarget(self, action: action, for: .touchUpInside) 61 | button.frame = CGRect(x: 0, y: 0, width: 64, height: 30) 62 | button.setTitle(text, for: .normal) 63 | button.titleLabel?.font = UIFont.systemFont(ofSize: 15) 64 | button.setTitleColor(UIColor(hexColor: "808080"), for: .highlighted) 65 | button.setTitleColor(UIColor.gray, for: .normal) 66 | 67 | let barButtonItem = UIBarButtonItem(customView: button) 68 | switch position { 69 | case .left: 70 | self.navigationItem.leftBarButtonItem = barButtonItem 71 | case .right: 72 | let item = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) 73 | item.width = -10 74 | self.navigationItem.rightBarButtonItems = [item ,barButtonItem] 75 | } 76 | } 77 | 78 | public func createBackBarButtonItem( 79 | normalImage: UIImage, 80 | highlightImage: UIImage, 81 | backTitle: String, 82 | action: Selector 83 | ) { 84 | let button = UIButton(type: .custom) 85 | button.frame = CGRect(x: 0, y: 0, width: 84, height: 44) 86 | button.setTitleColor(UIColor(hexColor: "808080"), for: .highlighted) 87 | button.setTitleColor(UIColor.lightGray, for: .normal) 88 | button.setTitle(backTitle, for: .normal) 89 | button.setTitle(backTitle, for: .highlighted) 90 | button.setImage(normalImage, for: .normal) 91 | button.setImage(highlightImage, for: .highlighted) 92 | let imageInset = UIEdgeInsetsMake(0, -20, 0, 60) 93 | let titleInset = UIEdgeInsetsMake(0, -45, 0, -15) 94 | button.imageEdgeInsets = imageInset 95 | button.titleEdgeInsets = titleInset 96 | button.addTarget(self, action: action, for: .touchUpInside) 97 | button.frame = CGRect(x: 0, y: 0, width: 64, height: 30) 98 | button.titleLabel?.font = UIFont.systemFont(ofSize: 15) 99 | button.contentHorizontalAlignment = .left 100 | 101 | let barButtonItem = UIBarButtonItem(customView: button) 102 | self.navigationItem.leftBarButtonItem = barButtonItem 103 | } 104 | 105 | @discardableResult 106 | public func pop(animated: Bool) -> UIViewController? { 107 | return self.navigationController?.popViewController(animated: animated) 108 | } 109 | 110 | @discardableResult 111 | public func popToRoot(animated: Bool) -> [UIViewController]? { 112 | return self.navigationController?.popToRootViewController(animated: animated); 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /DXPhotoPickerDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 95146FF71BD0F62900C8C45C /* UIView+DXFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95146FF61BD0F62900C8C45C /* UIView+DXFrame.swift */; }; 11 | 9518B3921BE9ED1C00B37E49 /* DXSelectedImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9518B3911BE9ED1C00B37E49 /* DXSelectedImageViewController.swift */; }; 12 | 9518B3941BE9ED3D00B37E49 /* DXSelectedImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9518B3931BE9ED3D00B37E49 /* DXSelectedImageCell.swift */; }; 13 | 9522C9BB1C340C7D0016A62D /* UICollectionView+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9522C9BA1C340C7D0016A62D /* UICollectionView+Convenience.swift */; }; 14 | 9541C6AC1BCD080D003102FC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9541C6AB1BCD080D003102FC /* AppDelegate.swift */; }; 15 | 9541C6AE1BCD080D003102FC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9541C6AD1BCD080D003102FC /* ViewController.swift */; }; 16 | 9541C6B11BCD080D003102FC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9541C6AF1BCD080D003102FC /* Main.storyboard */; }; 17 | 9541C6B31BCD080D003102FC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9541C6B21BCD080D003102FC /* Assets.xcassets */; }; 18 | 9541C6B61BCD080D003102FC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9541C6B41BCD080D003102FC /* LaunchScreen.storyboard */; }; 19 | 9541C6E01BCD0D23003102FC /* UIColor+DXHex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9541C6DF1BCD0D23003102FC /* UIColor+DXHex.swift */; }; 20 | 9554A02D1BEB2BB100DD6F6B /* DXPickerHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A02C1BEB2BB100DD6F6B /* DXPickerHelper.swift */; }; 21 | 9554A0371BEB2BBC00DD6F6B /* DXAlbumCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A02E1BEB2BBC00DD6F6B /* DXAlbumCell.swift */; }; 22 | 9554A0381BEB2BBC00DD6F6B /* DXAssetCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A02F1BEB2BBC00DD6F6B /* DXAssetCell.swift */; }; 23 | 9554A0391BEB2BBC00DD6F6B /* DXBadgeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A0301BEB2BBC00DD6F6B /* DXBadgeLabel.swift */; }; 24 | 9554A03A1BEB2BBC00DD6F6B /* DXBrowserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A0311BEB2BBC00DD6F6B /* DXBrowserCell.swift */; }; 25 | 9554A03B1BEB2BBC00DD6F6B /* DXFullImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A0321BEB2BBC00DD6F6B /* DXFullImageButton.swift */; }; 26 | 9554A03C1BEB2BBC00DD6F6B /* DXPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A0331BEB2BBC00DD6F6B /* DXPromptView.swift */; }; 27 | 9554A03D1BEB2BBC00DD6F6B /* DXSendButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A0341BEB2BBC00DD6F6B /* DXSendButton.swift */; }; 28 | 9554A03E1BEB2BBC00DD6F6B /* DXTapDetectingImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A0351BEB2BBC00DD6F6B /* DXTapDetectingImageView.swift */; }; 29 | 9554A03F1BEB2BBC00DD6F6B /* DXUnAuthorizedTipsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A0361BEB2BBC00DD6F6B /* DXUnAuthorizedTipsView.swift */; }; 30 | 9554A0441BEB2BC500DD6F6B /* DXAlbumTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A0401BEB2BC500DD6F6B /* DXAlbumTableViewController.swift */; }; 31 | 9554A0451BEB2BC500DD6F6B /* DXImageFlowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A0411BEB2BC500DD6F6B /* DXImageFlowViewController.swift */; }; 32 | 9554A0461BEB2BC500DD6F6B /* DXPhotoBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A0421BEB2BC500DD6F6B /* DXPhotoBrowser.swift */; }; 33 | 9554A0471BEB2BC500DD6F6B /* DXPhotoPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9554A0431BEB2BC500DD6F6B /* DXPhotoPickerController.swift */; }; 34 | 95643B311BCF54FF0091ACE8 /* DXUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95643B301BCF54FF0091ACE8 /* DXUtil.swift */; }; 35 | 956595BD1BCD4DCD00A74B30 /* DXPhotoPicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = 956595BF1BCD4DCD00A74B30 /* DXPhotoPicker.strings */; }; 36 | 95A3F1891BCD441300A43CE1 /* imageResources.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 95A3F1881BCD441300A43CE1 /* imageResources.xcassets */; }; 37 | 95A9A4301E214043008D6374 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9541C6DA1BCD084C003102FC /* Photos.framework */; }; 38 | 95D786A51BD931E900C7A5A8 /* UIViewController+DXPhotoPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95D786A41BD931E900C7A5A8 /* UIViewController+DXPhotoPicker.swift */; }; 39 | 95D9571C1BD758420012BD36 /* DXAlbum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95D9571B1BD758420012BD36 /* DXAlbum.swift */; }; 40 | /* End PBXBuildFile section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 95146FF61BD0F62900C8C45C /* UIView+DXFrame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+DXFrame.swift"; sourceTree = ""; }; 44 | 9518B3911BE9ED1C00B37E49 /* DXSelectedImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXSelectedImageViewController.swift; sourceTree = ""; }; 45 | 9518B3931BE9ED3D00B37E49 /* DXSelectedImageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXSelectedImageCell.swift; sourceTree = ""; }; 46 | 9522C9BA1C340C7D0016A62D /* UICollectionView+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Convenience.swift"; sourceTree = ""; }; 47 | 9541C6A81BCD080D003102FC /* DXPhotoPickerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DXPhotoPickerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 9541C6AB1BCD080D003102FC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 49 | 9541C6AD1BCD080D003102FC /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 50 | 9541C6B01BCD080D003102FC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 51 | 9541C6B21BCD080D003102FC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52 | 9541C6B51BCD080D003102FC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 53 | 9541C6B71BCD080D003102FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 9541C6DA1BCD084C003102FC /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; }; 55 | 9541C6DF1BCD0D23003102FC /* UIColor+DXHex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+DXHex.swift"; sourceTree = ""; }; 56 | 9554A02C1BEB2BB100DD6F6B /* DXPickerHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXPickerHelper.swift; sourceTree = ""; }; 57 | 9554A02E1BEB2BBC00DD6F6B /* DXAlbumCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXAlbumCell.swift; sourceTree = ""; }; 58 | 9554A02F1BEB2BBC00DD6F6B /* DXAssetCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXAssetCell.swift; sourceTree = ""; }; 59 | 9554A0301BEB2BBC00DD6F6B /* DXBadgeLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXBadgeLabel.swift; sourceTree = ""; }; 60 | 9554A0311BEB2BBC00DD6F6B /* DXBrowserCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXBrowserCell.swift; sourceTree = ""; }; 61 | 9554A0321BEB2BBC00DD6F6B /* DXFullImageButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXFullImageButton.swift; sourceTree = ""; }; 62 | 9554A0331BEB2BBC00DD6F6B /* DXPromptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXPromptView.swift; sourceTree = ""; }; 63 | 9554A0341BEB2BBC00DD6F6B /* DXSendButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXSendButton.swift; sourceTree = ""; }; 64 | 9554A0351BEB2BBC00DD6F6B /* DXTapDetectingImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXTapDetectingImageView.swift; sourceTree = ""; }; 65 | 9554A0361BEB2BBC00DD6F6B /* DXUnAuthorizedTipsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXUnAuthorizedTipsView.swift; sourceTree = ""; }; 66 | 9554A0401BEB2BC500DD6F6B /* DXAlbumTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXAlbumTableViewController.swift; sourceTree = ""; }; 67 | 9554A0411BEB2BC500DD6F6B /* DXImageFlowViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXImageFlowViewController.swift; sourceTree = ""; }; 68 | 9554A0421BEB2BC500DD6F6B /* DXPhotoBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXPhotoBrowser.swift; sourceTree = ""; }; 69 | 9554A0431BEB2BC500DD6F6B /* DXPhotoPickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXPhotoPickerController.swift; sourceTree = ""; }; 70 | 95643B301BCF54FF0091ACE8 /* DXUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXUtil.swift; sourceTree = ""; }; 71 | 956595BE1BCD4DCD00A74B30 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/DXPhotoPicker.strings"; sourceTree = ""; }; 72 | 956595C01BCD4DD100A74B30 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/DXPhotoPicker.strings; sourceTree = ""; }; 73 | 95A3F1881BCD441300A43CE1 /* imageResources.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = imageResources.xcassets; sourceTree = ""; }; 74 | 95D786A41BD931E900C7A5A8 /* UIViewController+DXPhotoPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+DXPhotoPicker.swift"; sourceTree = ""; }; 75 | 95D9571B1BD758420012BD36 /* DXAlbum.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DXAlbum.swift; sourceTree = ""; }; 76 | /* End PBXFileReference section */ 77 | 78 | /* Begin PBXFrameworksBuildPhase section */ 79 | 9541C6A51BCD080D003102FC /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | 95A9A4301E214043008D6374 /* Photos.framework in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | /* End PBXFrameworksBuildPhase section */ 88 | 89 | /* Begin PBXGroup section */ 90 | 9541C69F1BCD080D003102FC = { 91 | isa = PBXGroup; 92 | children = ( 93 | 9541C6DA1BCD084C003102FC /* Photos.framework */, 94 | 9541C6D91BCD081D003102FC /* DXPhotoPicker */, 95 | 9541C6AA1BCD080D003102FC /* DXPhotoPickerDemo */, 96 | 9541C6A91BCD080D003102FC /* Products */, 97 | ); 98 | sourceTree = ""; 99 | }; 100 | 9541C6A91BCD080D003102FC /* Products */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 9541C6A81BCD080D003102FC /* DXPhotoPickerDemo.app */, 104 | ); 105 | name = Products; 106 | sourceTree = ""; 107 | }; 108 | 9541C6AA1BCD080D003102FC /* DXPhotoPickerDemo */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 9541C6AB1BCD080D003102FC /* AppDelegate.swift */, 112 | 9541C6AD1BCD080D003102FC /* ViewController.swift */, 113 | 9518B3911BE9ED1C00B37E49 /* DXSelectedImageViewController.swift */, 114 | 9518B3931BE9ED3D00B37E49 /* DXSelectedImageCell.swift */, 115 | 9541C6AF1BCD080D003102FC /* Main.storyboard */, 116 | 9541C6B21BCD080D003102FC /* Assets.xcassets */, 117 | 9541C6B41BCD080D003102FC /* LaunchScreen.storyboard */, 118 | 9541C6B71BCD080D003102FC /* Info.plist */, 119 | ); 120 | path = DXPhotoPickerDemo; 121 | sourceTree = ""; 122 | }; 123 | 9541C6D91BCD081D003102FC /* DXPhotoPicker */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 9541C6DE1BCD0A43003102FC /* Resources */, 127 | 9541C6DD1BCD0A2B003102FC /* Support */, 128 | 9541C6DC1BCD0A00003102FC /* Classes */, 129 | ); 130 | path = DXPhotoPicker; 131 | sourceTree = ""; 132 | }; 133 | 9541C6DC1BCD0A00003102FC /* Classes */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 9554A02B1BEB2B5900DD6F6B /* Views */, 137 | 9554A0291BEB2B2000DD6F6B /* Controllers */, 138 | 95D9571A1BD752F70012BD36 /* Models */, 139 | ); 140 | path = Classes; 141 | sourceTree = ""; 142 | }; 143 | 9541C6DD1BCD0A2B003102FC /* Support */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 9554A02C1BEB2BB100DD6F6B /* DXPickerHelper.swift */, 147 | 9541C6DF1BCD0D23003102FC /* UIColor+DXHex.swift */, 148 | 95643B301BCF54FF0091ACE8 /* DXUtil.swift */, 149 | 95146FF61BD0F62900C8C45C /* UIView+DXFrame.swift */, 150 | 95D786A41BD931E900C7A5A8 /* UIViewController+DXPhotoPicker.swift */, 151 | 9522C9BA1C340C7D0016A62D /* UICollectionView+Convenience.swift */, 152 | ); 153 | path = Support; 154 | sourceTree = ""; 155 | }; 156 | 9541C6DE1BCD0A43003102FC /* Resources */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 95A3F1881BCD441300A43CE1 /* imageResources.xcassets */, 160 | 956595BF1BCD4DCD00A74B30 /* DXPhotoPicker.strings */, 161 | ); 162 | path = Resources; 163 | sourceTree = ""; 164 | }; 165 | 9554A0291BEB2B2000DD6F6B /* Controllers */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 9554A0431BEB2BC500DD6F6B /* DXPhotoPickerController.swift */, 169 | 9554A0401BEB2BC500DD6F6B /* DXAlbumTableViewController.swift */, 170 | 9554A0411BEB2BC500DD6F6B /* DXImageFlowViewController.swift */, 171 | 9554A0421BEB2BC500DD6F6B /* DXPhotoBrowser.swift */, 172 | ); 173 | path = Controllers; 174 | sourceTree = ""; 175 | }; 176 | 9554A02B1BEB2B5900DD6F6B /* Views */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | 9554A02E1BEB2BBC00DD6F6B /* DXAlbumCell.swift */, 180 | 9554A02F1BEB2BBC00DD6F6B /* DXAssetCell.swift */, 181 | 9554A0301BEB2BBC00DD6F6B /* DXBadgeLabel.swift */, 182 | 9554A0311BEB2BBC00DD6F6B /* DXBrowserCell.swift */, 183 | 9554A0321BEB2BBC00DD6F6B /* DXFullImageButton.swift */, 184 | 9554A0331BEB2BBC00DD6F6B /* DXPromptView.swift */, 185 | 9554A0341BEB2BBC00DD6F6B /* DXSendButton.swift */, 186 | 9554A0351BEB2BBC00DD6F6B /* DXTapDetectingImageView.swift */, 187 | 9554A0361BEB2BBC00DD6F6B /* DXUnAuthorizedTipsView.swift */, 188 | ); 189 | path = Views; 190 | sourceTree = ""; 191 | }; 192 | 95D9571A1BD752F70012BD36 /* Models */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | 95D9571B1BD758420012BD36 /* DXAlbum.swift */, 196 | ); 197 | path = Models; 198 | sourceTree = ""; 199 | }; 200 | /* End PBXGroup section */ 201 | 202 | /* Begin PBXNativeTarget section */ 203 | 9541C6A71BCD080D003102FC /* DXPhotoPickerDemo */ = { 204 | isa = PBXNativeTarget; 205 | buildConfigurationList = 9541C6D01BCD080D003102FC /* Build configuration list for PBXNativeTarget "DXPhotoPickerDemo" */; 206 | buildPhases = ( 207 | 9541C6A41BCD080D003102FC /* Sources */, 208 | 9541C6A51BCD080D003102FC /* Frameworks */, 209 | 9541C6A61BCD080D003102FC /* Resources */, 210 | ); 211 | buildRules = ( 212 | ); 213 | dependencies = ( 214 | ); 215 | name = DXPhotoPickerDemo; 216 | productName = DXPhotosPickerDemo; 217 | productReference = 9541C6A81BCD080D003102FC /* DXPhotoPickerDemo.app */; 218 | productType = "com.apple.product-type.application"; 219 | }; 220 | /* End PBXNativeTarget section */ 221 | 222 | /* Begin PBXProject section */ 223 | 9541C6A01BCD080D003102FC /* Project object */ = { 224 | isa = PBXProject; 225 | attributes = { 226 | CLASSPREFIX = DX; 227 | LastUpgradeCheck = 0800; 228 | ORGANIZATIONNAME = Dennis; 229 | TargetAttributes = { 230 | 9541C6A71BCD080D003102FC = { 231 | CreatedOnToolsVersion = 7.0.1; 232 | DevelopmentTeam = 7RQERF84NY; 233 | LastSwiftMigration = 0800; 234 | }; 235 | }; 236 | }; 237 | buildConfigurationList = 9541C6A31BCD080D003102FC /* Build configuration list for PBXProject "DXPhotoPickerDemo" */; 238 | compatibilityVersion = "Xcode 3.2"; 239 | developmentRegion = English; 240 | hasScannedForEncodings = 0; 241 | knownRegions = ( 242 | en, 243 | Base, 244 | "zh-Hans", 245 | ); 246 | mainGroup = 9541C69F1BCD080D003102FC; 247 | productRefGroup = 9541C6A91BCD080D003102FC /* Products */; 248 | projectDirPath = ""; 249 | projectRoot = ""; 250 | targets = ( 251 | 9541C6A71BCD080D003102FC /* DXPhotoPickerDemo */, 252 | ); 253 | }; 254 | /* End PBXProject section */ 255 | 256 | /* Begin PBXResourcesBuildPhase section */ 257 | 9541C6A61BCD080D003102FC /* Resources */ = { 258 | isa = PBXResourcesBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | 9541C6B61BCD080D003102FC /* LaunchScreen.storyboard in Resources */, 262 | 95A3F1891BCD441300A43CE1 /* imageResources.xcassets in Resources */, 263 | 9541C6B31BCD080D003102FC /* Assets.xcassets in Resources */, 264 | 9541C6B11BCD080D003102FC /* Main.storyboard in Resources */, 265 | 956595BD1BCD4DCD00A74B30 /* DXPhotoPicker.strings in Resources */, 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | }; 269 | /* End PBXResourcesBuildPhase section */ 270 | 271 | /* Begin PBXSourcesBuildPhase section */ 272 | 9541C6A41BCD080D003102FC /* Sources */ = { 273 | isa = PBXSourcesBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | 9554A0471BEB2BC500DD6F6B /* DXPhotoPickerController.swift in Sources */, 277 | 9541C6E01BCD0D23003102FC /* UIColor+DXHex.swift in Sources */, 278 | 9554A02D1BEB2BB100DD6F6B /* DXPickerHelper.swift in Sources */, 279 | 95D9571C1BD758420012BD36 /* DXAlbum.swift in Sources */, 280 | 9554A0391BEB2BBC00DD6F6B /* DXBadgeLabel.swift in Sources */, 281 | 9541C6AE1BCD080D003102FC /* ViewController.swift in Sources */, 282 | 95643B311BCF54FF0091ACE8 /* DXUtil.swift in Sources */, 283 | 9554A0441BEB2BC500DD6F6B /* DXAlbumTableViewController.swift in Sources */, 284 | 9554A03C1BEB2BBC00DD6F6B /* DXPromptView.swift in Sources */, 285 | 95146FF71BD0F62900C8C45C /* UIView+DXFrame.swift in Sources */, 286 | 9518B3941BE9ED3D00B37E49 /* DXSelectedImageCell.swift in Sources */, 287 | 9554A0371BEB2BBC00DD6F6B /* DXAlbumCell.swift in Sources */, 288 | 9554A0451BEB2BC500DD6F6B /* DXImageFlowViewController.swift in Sources */, 289 | 9554A0381BEB2BBC00DD6F6B /* DXAssetCell.swift in Sources */, 290 | 9554A0461BEB2BC500DD6F6B /* DXPhotoBrowser.swift in Sources */, 291 | 9554A03B1BEB2BBC00DD6F6B /* DXFullImageButton.swift in Sources */, 292 | 9554A03D1BEB2BBC00DD6F6B /* DXSendButton.swift in Sources */, 293 | 9554A03A1BEB2BBC00DD6F6B /* DXBrowserCell.swift in Sources */, 294 | 9522C9BB1C340C7D0016A62D /* UICollectionView+Convenience.swift in Sources */, 295 | 9554A03F1BEB2BBC00DD6F6B /* DXUnAuthorizedTipsView.swift in Sources */, 296 | 9541C6AC1BCD080D003102FC /* AppDelegate.swift in Sources */, 297 | 9518B3921BE9ED1C00B37E49 /* DXSelectedImageViewController.swift in Sources */, 298 | 9554A03E1BEB2BBC00DD6F6B /* DXTapDetectingImageView.swift in Sources */, 299 | 95D786A51BD931E900C7A5A8 /* UIViewController+DXPhotoPicker.swift in Sources */, 300 | ); 301 | runOnlyForDeploymentPostprocessing = 0; 302 | }; 303 | /* End PBXSourcesBuildPhase section */ 304 | 305 | /* Begin PBXVariantGroup section */ 306 | 9541C6AF1BCD080D003102FC /* Main.storyboard */ = { 307 | isa = PBXVariantGroup; 308 | children = ( 309 | 9541C6B01BCD080D003102FC /* Base */, 310 | ); 311 | name = Main.storyboard; 312 | sourceTree = ""; 313 | }; 314 | 9541C6B41BCD080D003102FC /* LaunchScreen.storyboard */ = { 315 | isa = PBXVariantGroup; 316 | children = ( 317 | 9541C6B51BCD080D003102FC /* Base */, 318 | ); 319 | name = LaunchScreen.storyboard; 320 | sourceTree = ""; 321 | }; 322 | 956595BF1BCD4DCD00A74B30 /* DXPhotoPicker.strings */ = { 323 | isa = PBXVariantGroup; 324 | children = ( 325 | 956595BE1BCD4DCD00A74B30 /* zh-Hans */, 326 | 956595C01BCD4DD100A74B30 /* en */, 327 | ); 328 | name = DXPhotoPicker.strings; 329 | sourceTree = ""; 330 | }; 331 | /* End PBXVariantGroup section */ 332 | 333 | /* Begin XCBuildConfiguration section */ 334 | 9541C6CE1BCD080D003102FC /* Debug */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | ALWAYS_SEARCH_USER_PATHS = NO; 338 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 339 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 340 | CLANG_CXX_LIBRARY = "libc++"; 341 | CLANG_ENABLE_MODULES = YES; 342 | CLANG_ENABLE_OBJC_ARC = YES; 343 | CLANG_WARN_BOOL_CONVERSION = YES; 344 | CLANG_WARN_CONSTANT_CONVERSION = YES; 345 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 346 | CLANG_WARN_EMPTY_BODY = YES; 347 | CLANG_WARN_ENUM_CONVERSION = YES; 348 | CLANG_WARN_INFINITE_RECURSION = YES; 349 | CLANG_WARN_INT_CONVERSION = YES; 350 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 351 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 352 | CLANG_WARN_UNREACHABLE_CODE = YES; 353 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 354 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 355 | COPY_PHASE_STRIP = NO; 356 | DEBUG_INFORMATION_FORMAT = dwarf; 357 | ENABLE_STRICT_OBJC_MSGSEND = YES; 358 | ENABLE_TESTABILITY = YES; 359 | GCC_C_LANGUAGE_STANDARD = gnu99; 360 | GCC_DYNAMIC_NO_PIC = NO; 361 | GCC_NO_COMMON_BLOCKS = YES; 362 | GCC_OPTIMIZATION_LEVEL = 0; 363 | GCC_PREPROCESSOR_DEFINITIONS = ( 364 | "DEBUG=1", 365 | "$(inherited)", 366 | ); 367 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 368 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 369 | GCC_WARN_UNDECLARED_SELECTOR = YES; 370 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 371 | GCC_WARN_UNUSED_FUNCTION = YES; 372 | GCC_WARN_UNUSED_VARIABLE = YES; 373 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 374 | MTL_ENABLE_DEBUG_INFO = YES; 375 | ONLY_ACTIVE_ARCH = YES; 376 | SDKROOT = iphoneos; 377 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 378 | }; 379 | name = Debug; 380 | }; 381 | 9541C6CF1BCD080D003102FC /* Release */ = { 382 | isa = XCBuildConfiguration; 383 | buildSettings = { 384 | ALWAYS_SEARCH_USER_PATHS = NO; 385 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 387 | CLANG_CXX_LIBRARY = "libc++"; 388 | CLANG_ENABLE_MODULES = YES; 389 | CLANG_ENABLE_OBJC_ARC = YES; 390 | CLANG_WARN_BOOL_CONVERSION = YES; 391 | CLANG_WARN_CONSTANT_CONVERSION = YES; 392 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 393 | CLANG_WARN_EMPTY_BODY = YES; 394 | CLANG_WARN_ENUM_CONVERSION = YES; 395 | CLANG_WARN_INFINITE_RECURSION = YES; 396 | CLANG_WARN_INT_CONVERSION = YES; 397 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 398 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 399 | CLANG_WARN_UNREACHABLE_CODE = YES; 400 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 401 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 402 | COPY_PHASE_STRIP = NO; 403 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 404 | ENABLE_NS_ASSERTIONS = NO; 405 | ENABLE_STRICT_OBJC_MSGSEND = YES; 406 | GCC_C_LANGUAGE_STANDARD = gnu99; 407 | GCC_NO_COMMON_BLOCKS = YES; 408 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 409 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 410 | GCC_WARN_UNDECLARED_SELECTOR = YES; 411 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 412 | GCC_WARN_UNUSED_FUNCTION = YES; 413 | GCC_WARN_UNUSED_VARIABLE = YES; 414 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 415 | MTL_ENABLE_DEBUG_INFO = NO; 416 | SDKROOT = iphoneos; 417 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 418 | VALIDATE_PRODUCT = YES; 419 | }; 420 | name = Release; 421 | }; 422 | 9541C6D11BCD080D003102FC /* Debug */ = { 423 | isa = XCBuildConfiguration; 424 | buildSettings = { 425 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 426 | CODE_SIGN_IDENTITY = "iPhone Developer"; 427 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 428 | INFOPLIST_FILE = "$(SRCROOT)/DXPhotoPickerDemo/Info.plist"; 429 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 430 | OTHER_SWIFT_FLAGS = "-D DEBUG"; 431 | PRODUCT_BUNDLE_IDENTIFIER = com.dennis.DXPhotoPickerDenS; 432 | PRODUCT_NAME = DXPhotoPickerDemo; 433 | PROVISIONING_PROFILE = ""; 434 | SWIFT_VERSION = 3.0; 435 | }; 436 | name = Debug; 437 | }; 438 | 9541C6D21BCD080D003102FC /* Release */ = { 439 | isa = XCBuildConfiguration; 440 | buildSettings = { 441 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 442 | CODE_SIGN_IDENTITY = "iPhone Developer"; 443 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 444 | INFOPLIST_FILE = "$(SRCROOT)/DXPhotoPickerDemo/Info.plist"; 445 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 446 | PRODUCT_BUNDLE_IDENTIFIER = com.dennis.DXPhotoPickerDenS; 447 | PRODUCT_NAME = DXPhotoPickerDemo; 448 | PROVISIONING_PROFILE = ""; 449 | SWIFT_VERSION = 3.0; 450 | }; 451 | name = Release; 452 | }; 453 | /* End XCBuildConfiguration section */ 454 | 455 | /* Begin XCConfigurationList section */ 456 | 9541C6A31BCD080D003102FC /* Build configuration list for PBXProject "DXPhotoPickerDemo" */ = { 457 | isa = XCConfigurationList; 458 | buildConfigurations = ( 459 | 9541C6CE1BCD080D003102FC /* Debug */, 460 | 9541C6CF1BCD080D003102FC /* Release */, 461 | ); 462 | defaultConfigurationIsVisible = 0; 463 | defaultConfigurationName = Release; 464 | }; 465 | 9541C6D01BCD080D003102FC /* Build configuration list for PBXNativeTarget "DXPhotoPickerDemo" */ = { 466 | isa = XCConfigurationList; 467 | buildConfigurations = ( 468 | 9541C6D11BCD080D003102FC /* Debug */, 469 | 9541C6D21BCD080D003102FC /* Release */, 470 | ); 471 | defaultConfigurationIsVisible = 0; 472 | defaultConfigurationName = Release; 473 | }; 474 | /* End XCConfigurationList section */ 475 | }; 476 | rootObject = 9541C6A01BCD080D003102FC /* Project object */; 477 | } 478 | -------------------------------------------------------------------------------- /DXPhotoPickerDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DXPhotoPickerDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DXPhotosPickerDemo 4 | // 5 | // Created by Ding Xiao on 15/10/13. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | private func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-29@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-29@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "40x40", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-40@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-40@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "60x60", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-60@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-60@3x.png", 37 | "scale" : "3x" 38 | } 39 | ], 40 | "info" : { 41 | "version" : 1, 42 | "author" : "xcode" 43 | } 44 | } -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPickerDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Assets.xcassets/back.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "back.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Assets.xcassets/back.imageset/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPickerDemo/Assets.xcassets/back.imageset/back.png -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Assets.xcassets/default.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "default.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Assets.xcassets/default.imageset/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/DXPhotoPickerDemo/Assets.xcassets/default.imageset/default.png -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 39 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /DXPhotoPickerDemo/DXSelectedImageCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXSelectedImageCell.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by Ding Xiao on 15/11/4. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DXSelectedImageCell: UICollectionViewCell { 12 | 13 | @IBOutlet weak var infoLabel: UILabel! 14 | @IBOutlet weak var selectedImageView: UIImageView! 15 | } 16 | -------------------------------------------------------------------------------- /DXPhotoPickerDemo/DXSelectedImageViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DXSelectedImageViewController.swift 3 | // DXPhotoPickerDemo 4 | // 5 | // Created by Ding Xiao on 15/11/4. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | private let reuseIdentifier = "DXSelectedImageCell" 13 | 14 | class DXSelectedImageViewController: UICollectionViewController { 15 | 16 | var selectedImages: [PHAsset]? 17 | var isFullImage = false 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | } 22 | 23 | @objc private func back() { 24 | self.pop(animated: true) 25 | } 26 | 27 | override func didReceiveMemoryWarning() { 28 | super.didReceiveMemoryWarning() 29 | // Dispose of any resources that can be recreated. 30 | } 31 | 32 | // MARK: UICollectionViewDataSource 33 | 34 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 35 | guard selectedImages != nil else { 36 | return 0 37 | } 38 | return selectedImages!.count 39 | } 40 | 41 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 42 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! DXSelectedImageCell 43 | DXPickerHelper.fetchImage(viaAsset: selectedImages![indexPath.row], targetSize: CGSize(width:150, height: 150)) { (image) -> Void in 44 | cell.selectedImageView.image = image 45 | } 46 | 47 | if isFullImage { 48 | DXPickerHelper.fetchImageSize(asset: selectedImages![indexPath.row]) { (imageSize, sizeString) -> Void in 49 | cell.infoLabel.text = "imageSize: " + sizeString 50 | } 51 | } else { 52 | cell.infoLabel.text = "not full image" 53 | } 54 | return cell 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /DXPhotoPickerDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSCameraUsageDescription 26 | 我用一下你的相机 27 | NSPhotoLibraryUsageDescription 28 | 我想用你的相册 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /DXPhotoPickerDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DXPhotosPickerDemo 4 | // 5 | // Created by Ding Xiao on 15/10/13. 6 | // Copyright © 2015年 Dennis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class ViewController: UIViewController, DXPhotoPickerControllerDelegate { 13 | // MARK: ui actions 14 | 15 | 16 | @IBAction func addPhotos(_ sender: UIButton) { 17 | let picker = DXPhotoPickerController() 18 | picker.photoPickerDelegate = self 19 | self.present(picker, animated: true, completion: nil) 20 | } 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | } 25 | 26 | @IBAction func aboutAction(_ sender: UIButton) { 27 | UIApplication.shared.openURL(URL(string: "http://weibo.com/GreatDingXiao")!) 28 | } 29 | 30 | // MARK: DXPhototPickerControllerDelegate 31 | func photoPickerDidCancel(photoPicker: DXPhotoPickerController) { 32 | photoPicker.dismiss(animated: true, completion: nil) 33 | } 34 | 35 | func photoPickerController(photoPicker photosPicker: DXPhotoPickerController?, 36 | sendImages: [PHAsset]?, 37 | isFullImage: Bool) { 38 | photosPicker?.dismiss(animated: true, completion: nil) 39 | let vc = storyboard?.instantiateViewController(withIdentifier: "DXSelectedImageViewController") as! DXSelectedImageViewController 40 | vc.selectedImages = sendImages 41 | vc.isFullImage = isFullImage 42 | navigationController?.pushViewController(vc, animated: true) 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dennis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DXPhotoPickerController 2 | 3 | ![logo](https://github.com/AwesomeDennis/DXPhotoPicker/blob/master/ScreenShot/Icon.png) 4 | 5 | ## About 6 | DXPhotoPickerController is a component that funtioned like imagepicker in WeChat. It's very similar to UIImagePickerController which makes its usage simple and concise. 7 | 8 | DXPhotoPickerController is a swift version of [DNImagePicker](https://github.com/AwesomeDennis/DNImagePicker). 9 | 10 | I don't make it as a pod because of the flexible UI requirements. Only supported the simple demo and you can customize your own style. 11 | 12 | ![image](https://github.com/AwesomeDennis/DXPhotoPicker/blob/master/ScreenShot/ScreenShot.gif) 13 | 14 | ##Version 15 | The current version can support Swift 3. If you want the Swift 2.2 one, [click here](https://github.com/AwesomeDennis/DXPhotoPicker/releases/tag/1.0) 16 | 17 | 18 | ## Usage 19 | `DXPhotoPickerController` is similar to `UIImagePickerController` in its usage. 20 | 21 | ### Example 22 | ``` 23 | let picker = DXPhotoPickerController() 24 | picker.photoPickerDelegate = self 25 | self.present(picker, animated: true, completion: nil) 26 | ``` 27 | ``` 28 | // MARK: DXPhototPickerControllerDelegate 29 | func photoPickerDidCancel(photoPicker: DXPhotoPickerController) { 30 | photoPicker.dismiss(animated: true, completion: nil) 31 | } 32 | 33 | func photoPickerController(photoPicker photosPicker: DXPhotoPickerController?, 34 | sendImages: [PHAsset]?, 35 | isFullImage: Bool) { 36 | photosPicker?.dismiss(animated: true, completion: nil) 37 | let vc = storyboard?.instantiateViewController(withIdentifier: "DXSelectedImageViewController") as! DXSelectedImageViewController 38 | vc.selectedImages = sendImages 39 | vc.isFullImage = isFullImage 40 | navigationController?.pushViewController(vc, animated: true) 41 | } 42 | 43 | 44 | ``` 45 | 46 | The call back delegte methods 47 | ``` 48 | func photoPickerController(photoPicker photosPicker: DXPhotoPickerController?, 49 | sendImages: [PHAsset]?, 50 | isFullImage: Bool) 51 | ``` 52 | param `isFullImage` suggested if the image you choose is the high quality image. 53 | 54 | ## Requirements 55 | DXPhotoPickerController is written in Swift and links against `Photos.framework`. It therefore requires iOS 8 or later. 56 | 57 | ##Tips 58 | Add `Photo Library Usage Description` key in `Info.plist` after iOS 10, or it will crash. 59 | 60 | ## Author 61 | 我叫丁晓, [Weibo](http://weibo.com/GreatDingXiao). 62 | 😄 63 | 64 | ## Inspired 65 | [mwaterfall/MWPhotoBrowser](https://github.com/mwaterfall/MWPhotoBrowser) gave a me great help! 66 | -------------------------------------------------------------------------------- /ScreenShot/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/ScreenShot/Icon.png -------------------------------------------------------------------------------- /ScreenShot/ScreenShot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisxiaoding/DXPhotoPicker/4a413846f495ade2f006839db678d4f1d5279dc5/ScreenShot/ScreenShot.gif --------------------------------------------------------------------------------