├── .swift-version ├── Images ├── demo1.png ├── demo2.png └── logo.png ├── LPAlbum ├── Others │ ├── LPAlbum.bundle │ │ ├── Images │ │ │ ├── meun_down@2x.png │ │ │ ├── meun_down@3x.png │ │ │ ├── image_camera@2x.png │ │ │ ├── image_camera@3x.png │ │ │ ├── circle_normal@2x.png │ │ │ ├── circle_normal@3x.png │ │ │ ├── circle_selected@2x.png │ │ │ └── circle_selected@3x.png │ │ ├── en.lproj │ │ │ └── Localizable.strings │ │ └── zh.lproj │ │ │ └── Localizable.strings │ ├── AlbumError.swift │ ├── Config.swift │ ├── AuthorizationTool.swift │ ├── Models.swift │ ├── AlbumManager.swift │ └── Extensions.swift ├── LPAlbum.h ├── Info.plist ├── Views │ ├── TakeCameraCell.swift │ ├── TitleView.swift │ ├── AlbumCollectionCell.swift │ ├── PhotoPreviewCell.swift │ └── DropMenuView.swift └── Controllers │ ├── PhotoPreviewController.swift │ ├── LPNavigationController.swift │ ├── PhotosBrowerController.swift │ └── LPAlbum.swift ├── LPAlbum.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── LPAlbumDemo ├── AppDelegate.swift ├── PhotoCell.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard └── ViewController.swift ├── LPAlbumTests ├── Info.plist └── LPAlbumTests.swift ├── LPAlbum.podspec ├── LICENSE ├── .gitignore └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /Images/demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopeer/LPAlbum/HEAD/Images/demo1.png -------------------------------------------------------------------------------- /Images/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopeer/LPAlbum/HEAD/Images/demo2.png -------------------------------------------------------------------------------- /Images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopeer/LPAlbum/HEAD/Images/logo.png -------------------------------------------------------------------------------- /LPAlbum/Others/LPAlbum.bundle/Images/meun_down@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopeer/LPAlbum/HEAD/LPAlbum/Others/LPAlbum.bundle/Images/meun_down@2x.png -------------------------------------------------------------------------------- /LPAlbum/Others/LPAlbum.bundle/Images/meun_down@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopeer/LPAlbum/HEAD/LPAlbum/Others/LPAlbum.bundle/Images/meun_down@3x.png -------------------------------------------------------------------------------- /LPAlbum/Others/LPAlbum.bundle/Images/image_camera@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopeer/LPAlbum/HEAD/LPAlbum/Others/LPAlbum.bundle/Images/image_camera@2x.png -------------------------------------------------------------------------------- /LPAlbum/Others/LPAlbum.bundle/Images/image_camera@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopeer/LPAlbum/HEAD/LPAlbum/Others/LPAlbum.bundle/Images/image_camera@3x.png -------------------------------------------------------------------------------- /LPAlbum/Others/LPAlbum.bundle/Images/circle_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopeer/LPAlbum/HEAD/LPAlbum/Others/LPAlbum.bundle/Images/circle_normal@2x.png -------------------------------------------------------------------------------- /LPAlbum/Others/LPAlbum.bundle/Images/circle_normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopeer/LPAlbum/HEAD/LPAlbum/Others/LPAlbum.bundle/Images/circle_normal@3x.png -------------------------------------------------------------------------------- /LPAlbum/Others/LPAlbum.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopeer/LPAlbum/HEAD/LPAlbum/Others/LPAlbum.bundle/en.lproj/Localizable.strings -------------------------------------------------------------------------------- /LPAlbum/Others/LPAlbum.bundle/zh.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopeer/LPAlbum/HEAD/LPAlbum/Others/LPAlbum.bundle/zh.lproj/Localizable.strings -------------------------------------------------------------------------------- /LPAlbum/Others/LPAlbum.bundle/Images/circle_selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopeer/LPAlbum/HEAD/LPAlbum/Others/LPAlbum.bundle/Images/circle_selected@2x.png -------------------------------------------------------------------------------- /LPAlbum/Others/LPAlbum.bundle/Images/circle_selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loopeer/LPAlbum/HEAD/LPAlbum/Others/LPAlbum.bundle/Images/circle_selected@3x.png -------------------------------------------------------------------------------- /LPAlbum.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LPAlbum/LPAlbum.h: -------------------------------------------------------------------------------- 1 | // 2 | // LPAlbum.h 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/8. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for LPAlbum. 12 | FOUNDATION_EXPORT double LPAlbumVersionNumber; 13 | 14 | //! Project version string for LPAlbum. 15 | FOUNDATION_EXPORT const unsigned char LPAlbumVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /LPAlbumDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // LPAlbumDemo 4 | // 5 | // Created by 郜宇 on 2017/9/8. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import LPAlbum 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | 19 | return true 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /LPAlbumDemo/PhotoCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoCell.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/13. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PhotoCell: UICollectionViewCell { 12 | 13 | let photoView = UIImageView() 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | 18 | photoView.frame = bounds 19 | photoView.clipsToBounds = true 20 | photoView.contentMode = .scaleAspectFit 21 | contentView.addSubview(photoView) 22 | } 23 | 24 | required init?(coder aDecoder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /LPAlbum/Others/AlbumError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LPAlbumError.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/11. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum AlbumError: Error { 12 | case noAlbumPermission 13 | case noCameraPermission 14 | case moreThanLargestChoiceCount 15 | case savePhotoError 16 | 17 | public var localizedDescription: String { 18 | switch self { 19 | case .noAlbumPermission: return String.local("没有相册访问权限") 20 | case .noCameraPermission: return String.local("没有摄像头访问权限") 21 | case .moreThanLargestChoiceCount: return String.local("达到了图片选择最大数量") 22 | case .savePhotoError: return String.local("保存图片失败") 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LPAlbumTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LPAlbum/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /LPAlbum.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "LPAlbum" 4 | s.version = "0.0.9" 5 | s.summary = "Can a decent enough photo album, photo album multi-select" 6 | 7 | s.homepage = "https://github.com/loopeer/LPAlbum" 8 | s.license = { :type => "MIT", :file => "LICENSE" } 9 | 10 | s.authors = { "gaoyu" => "gaoyu@loopeer.com" } 11 | s.social_media_url = "https://github.com/loopeer/LPAlbum" 12 | s.source = { :git => "https://github.com/loopeer/LPAlbum.git", :tag => s.version } 13 | 14 | s.source_files = ["LPAlbum/**/*.swift", "LPAlbum/LPAlbum.h"] 15 | s.public_header_files = ["LPAlbum/LPAlbum.h"] 16 | s.resource = 'LPAlbum/Others/LPAlbum.bundle' 17 | 18 | s.ios.deployment_target = "9.0" 19 | s.requires_arc = true 20 | 21 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '4.0' } 22 | 23 | end 24 | -------------------------------------------------------------------------------- /LPAlbum/Views/TakeCameraCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TakeCameraCell.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/12. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TakeCameraCell: UICollectionViewCell { 12 | 13 | private let iconView = UIImageView(image: Bundle.imageFromBundle("image_camera")) 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | contentView.backgroundColor = .black 18 | contentView.addSubview(iconView) 19 | } 20 | 21 | override func layoutSubviews() { 22 | super.layoutSubviews() 23 | iconView.bounds = bounds.applying(CGAffineTransform(scaleX: 0.5, y: 0.5)) 24 | iconView.center = CGPoint(x: bounds.width * 0.5, y: bounds.height * 0.5) 25 | } 26 | 27 | required init?(coder aDecoder: NSCoder) { 28 | fatalError("init(coder:) has not been implemented") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LPAlbumDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /LPAlbumTests/LPAlbumTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LPAlbumTests.swift 3 | // LPAlbumTests 4 | // 5 | // Created by 郜宇 on 2017/9/8. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LPAlbum 11 | 12 | class LPAlbumTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Loopeer 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 | -------------------------------------------------------------------------------- /LPAlbum/Controllers/PhotoPreviewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoPreviewController.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/26. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PhotoPreviewController: UIViewController { 12 | 13 | var assetModel: AssetModel! 14 | 15 | private let preview = UIImageView() 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | preview.layer.cornerRadius = 8 20 | preview.contentMode = .scaleAspectFill 21 | view.addSubview(preview) 22 | 23 | let scale = assetModel.asset.pixelHeight.cgFloat / assetModel.asset.pixelWidth.cgFloat 24 | preferredContentSize = CGSize(width: view.bounds.width, height: view.bounds.width * scale) 25 | preview.frame = CGRect(origin: .zero, size: preferredContentSize) 26 | 27 | AlbumManager.getPhoto(asset: assetModel.asset, targetSize: preferredContentSize, adaptScale: true, option: nil, contentMode: .aspectFill) { (image, _) in 28 | self.preview.image = image 29 | } 30 | } 31 | deinit { 32 | print("\(self) deinit") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LPAlbum/Controllers/LPNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LPNavigationController.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/8. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LPNavigationController: UINavigationController { 12 | 13 | override var preferredStatusBarStyle: UIStatusBarStyle { 14 | return LPAlbum.Style.statusBarStyle 15 | } 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | navigationBar.isTranslucent = true 20 | modalPresentationCapturesStatusBarAppearance = true 21 | } 22 | 23 | override init(rootViewController: UIViewController) { 24 | super.init(rootViewController: rootViewController) 25 | navigationBar.barTintColor = LPAlbum.Style.barTintColor 26 | navigationBar.tintColor = LPAlbum.Style.tintColor 27 | navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: LPAlbum.Style.barTitleColor] 28 | } 29 | 30 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 31 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 32 | 33 | } 34 | 35 | required init?(coder aDecoder: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LPAlbumDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSCameraUsageDescription 6 | 获取相机权限 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | CFBundleAllowMixedLocalizations 40 | 41 | NSPhotoLibraryUsageDescription 42 | 获取相册权限 43 | 44 | 45 | -------------------------------------------------------------------------------- /LPAlbum/Others/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/13. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension LPAlbum { 12 | public struct Config { 13 | /// 最大选择数量 14 | public var maxSelectCount: Int = 6 15 | /// 每列照片数量 16 | public var columnCount: Int = 4 17 | /// 照片间距 18 | public var photoPadding: CGFloat = 2.0 19 | /// 是否有相机 20 | public var hasCamera: Bool = true 21 | } 22 | 23 | public struct Style { 24 | /// `NavigationBar`标题颜色 25 | public static var barTitleColor: UIColor = UIColor.white 26 | /// `NavigationBar`背景颜色 27 | public static var barTintColor: UIColor = UIColor.darkGray 28 | /// `NavigationBar`item文本颜色 29 | public static var tintColor: UIColor = UIColor.white 30 | /// 状态栏样式 31 | public static var statusBarStyle: UIStatusBarStyle = .lightContent 32 | /// 下拉箭头图片 33 | public static var arrowImage: UIImage = Bundle.imageFromBundle("meun_down")! 34 | /// 正常的选择框图片 35 | public static var normalBox: UIImage = Bundle.imageFromBundle("circle_normal")! 36 | /// 选中的选择框图片 37 | public static var selectedBox: UIImage = Bundle.imageFromBundle("circle_selected")! 38 | /// 选择框box的可点击区域向外的扩展size 39 | public static var boxEdgeInsets = UIEdgeInsets(top: -5, left: -5, bottom: -5, right: -5) 40 | } 41 | 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /LPAlbum/Others/AuthorizationTool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthorizationTool.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/8. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Photos 11 | 12 | class AuthorizationTool { 13 | /// 相机权限 14 | static func cameraRequestAuthorization(queue: DispatchQueue = .main, complete: @escaping (_ status: AVAuthorizationStatus) -> Void) { 15 | switch AVCaptureDevice.authorizationStatus(for: AVMediaType.video) { 16 | case .authorized: complete(.authorized) 17 | case .denied: complete(.denied) 18 | case .restricted: complete(.restricted) 19 | case .notDetermined: 20 | queue.suspend() 21 | AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { (granted) in 22 | DispatchQueue.main.async { complete( granted ? .authorized : .denied ) } 23 | queue.resume() 24 | }) 25 | } 26 | } 27 | 28 | /// 相册权限 29 | static func albumRRequestAuthorization(queue: DispatchQueue = .main, complete: @escaping (_ status: PHAuthorizationStatus) -> Void){ 30 | switch PHPhotoLibrary.authorizationStatus() { 31 | case .authorized: complete(.authorized) 32 | case .denied: complete(.denied) 33 | case .restricted: complete(.restricted) 34 | case .notDetermined: 35 | queue.suspend() 36 | PHPhotoLibrary.requestAuthorization({ (status) in 37 | DispatchQueue.main.async { complete(status) } 38 | queue.resume() 39 | }) 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /LPAlbumDemo/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 | -------------------------------------------------------------------------------- /LPAlbum/Views/TitleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionTitleView.swift 3 | // MaiDou 4 | // 5 | // Created by 郜宇 on 2017/8/22. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TitleView: UIButton { 12 | 13 | override var intrinsicContentSize: CGSize { return UILayoutFittingExpandedSize } 14 | 15 | override var isSelected: Bool { 16 | didSet{ 17 | UIView.animate(withDuration: 0.25) { 18 | self.imageView?.transform = self.isSelected ? CGAffineTransform(rotationAngle: CGFloat.pi) : .identity 19 | } 20 | } 21 | } 22 | 23 | var title: String! { 24 | didSet{ 25 | setTitle(title, for: .normal) 26 | sizeToFit() 27 | bounds = CGRect(x: 0, y: 0, width: min(bounds.width + 10, 300) , height: bounds.height) 28 | } 29 | } 30 | 31 | var clickAction: ((TitleView) -> Void)? 32 | 33 | override init(frame: CGRect) { 34 | super.init(frame: frame) 35 | setImage(LPAlbum.Style.arrowImage, for: .normal) 36 | setTitleColor(LPAlbum.Style.barTitleColor, for: .normal) 37 | titleLabel?.font = UIFont.systemFont(ofSize: 17) 38 | bounds = CGRect(origin: .zero, size: CGSize(width: 300, height: 40)) 39 | addTarget(self, action: #selector(click), for: .touchUpInside) 40 | } 41 | 42 | @objc func click() { clickAction?(self) } 43 | 44 | required init?(coder aDecoder: NSCoder) { 45 | fatalError("init(coder:) has not been implemented") 46 | } 47 | 48 | override func layoutSubviews() { 49 | super.layoutSubviews() 50 | imageEdgeInsets = UIEdgeInsets(top: 0, left: (titleLabel?.frame.width ?? 0) + 5, bottom: 0, right: -(titleLabel?.frame.width ?? 0)) 51 | titleEdgeInsets = UIEdgeInsets(top: 0, left: -(imageView?.frame.width ?? 0), bottom: 0, right: (imageView?.frame.width ?? 0) + 5) 52 | contentHorizontalAlignment = .center 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /LPAlbum/Views/AlbumCollectionCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumCollectionCell.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/11. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AlbumCollectionCell: UICollectionViewCell { 12 | 13 | let photoView = UIImageView() 14 | let iconButton = UIButton() 15 | 16 | var assetIdentifier: String! 17 | 18 | var iconClickAction: ((Bool) -> Void)? 19 | 20 | func set(_ assetModel: AssetModel){ 21 | self.assetIdentifier = assetModel.asset.localIdentifier 22 | AlbumManager.getPhoto(asset: assetModel.asset, targetSize: bounds.size) {[weak self] (image, _) in 23 | guard let `self` = self else { return } 24 | guard self.assetIdentifier == assetModel.asset.localIdentifier else { return } 25 | self.photoView.image = image 26 | } 27 | iconButton.isSelected = assetModel.isSelect 28 | } 29 | 30 | override init(frame: CGRect) { 31 | super.init(frame: frame) 32 | photoView.contentMode = .scaleAspectFill 33 | photoView.clipsToBounds = true 34 | photoView.frame = CGRect(origin: .zero, size: frame.size) 35 | 36 | let padding = 5.cgFloat 37 | let iconWH = 20.adapt 38 | 39 | iconButton.frame = CGRect(x: frame.width - padding - iconWH, y: padding, width: iconWH, height: iconWH) 40 | iconButton.setBackgroundImage(LPAlbum.Style.normalBox , for: .normal) 41 | iconButton.setBackgroundImage(LPAlbum.Style.selectedBox, for: .selected) 42 | iconButton.lp_hitEdgeInsets = LPAlbum.Style.boxEdgeInsets 43 | 44 | addSubview(photoView) 45 | addSubview(iconButton) 46 | 47 | iconButton.addTarget(self, action: #selector(iconTap), for: .touchUpInside) 48 | } 49 | 50 | required init?(coder aDecoder: NSCoder) { 51 | fatalError("init(coder:) has not been implemented") 52 | } 53 | 54 | override func prepareForReuse() { 55 | super.prepareForReuse() 56 | photoView.image = nil 57 | } 58 | 59 | @objc func iconTap() { iconClickAction?(iconButton.isSelected) } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /LPAlbum/Others/Models.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Models.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/11. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Photos 11 | 12 | public struct AssetModel { 13 | /// 照片 14 | var asset: PHAsset 15 | /// 是否被选中 16 | var isSelect: Bool 17 | } 18 | 19 | public struct AlbumModel { 20 | /// 相册名字 21 | var name: String 22 | /// 相册封面 23 | var cover: PHAsset 24 | /// 照片数量 25 | var count: Int 26 | /// 照片Model 27 | var assetModels: [AssetModel] 28 | /// 该相册中被选中的数量 29 | var selectCount: Int { return assetModels.filter{ $0.isSelect == true }.count } 30 | } 31 | 32 | extension PHAsset { 33 | 34 | /// `PHAsset`转变成`AssetModel` 35 | var toAssetModel: AssetModel { return AssetModel(asset: self, isSelect: false) } 36 | } 37 | 38 | extension PHFetchResult where ObjectType == PHAsset { 39 | 40 | /// `PHFetchResult`转变成`[AssetModel]` 41 | var toAssetModels: [AssetModel] { 42 | var result = [AssetModel]() 43 | enumerateObjects({ (asset, i, stop) in 44 | result.append(asset.toAssetModel) 45 | }) 46 | return result 47 | } 48 | } 49 | 50 | extension Array where Iterator.Element == AlbumModel { 51 | 52 | /// 改变AlbumModel数组中的所有assetModel 53 | /// 54 | /// - Parameter assetModel: 传入的assetModel 55 | /// - Returns: 改变后的AlbumModel数组 56 | func change(assetModel: AssetModel) -> [AlbumModel] { 57 | 58 | return map{ 59 | var albumModel = $0 60 | albumModel.assetModels = $0.assetModels.map{ 61 | return $0.asset.localIdentifier == assetModel.asset.localIdentifier ? assetModel : $0 62 | } 63 | return albumModel 64 | } 65 | } 66 | 67 | /// 得到相册中所有的不重复的AssetModel 68 | var allSelectedAssetModels: [AssetModel] { 69 | return self[0].assetModels.filter{ $0.isSelect == true } 70 | } 71 | /// 得到所有的选中的`PHAsset` 72 | var allSelectedAsset: [PHAsset] { 73 | return allSelectedAssetModels.map{ $0.asset } 74 | } 75 | } 76 | 77 | extension Array where Iterator.Element == PHAsset { 78 | @discardableResult 79 | mutating func remove(_ element: PHAsset) -> PHAsset? { 80 | guard let index = index(of: element) else { return nil} 81 | return remove(at: index) 82 | } 83 | } 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/macos,swift 3 | 4 | ### macOS ### 5 | *.DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | ### Swift ### 32 | # Xcode 33 | # 34 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 35 | 36 | ## Build generated 37 | build/ 38 | DerivedData/ 39 | 40 | ## Various settings 41 | *.pbxuser 42 | !default.pbxuser 43 | *.mode1v3 44 | !default.mode1v3 45 | *.mode2v3 46 | !default.mode2v3 47 | *.perspectivev3 48 | !default.perspectivev3 49 | xcuserdata/ 50 | 51 | ## Other 52 | *.moved-aside 53 | *.xccheckout 54 | *.xcscmblueprint 55 | 56 | ## Obj-C/Swift specific 57 | *.hmap 58 | *.ipa 59 | *.dSYM.zip 60 | *.dSYM 61 | 62 | ## Playgrounds 63 | timeline.xctimeline 64 | playground.xcworkspace 65 | 66 | # Swift Package Manager 67 | # 68 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 69 | # Packages/ 70 | # Package.pins 71 | .build/ 72 | 73 | # CocoaPods - Refactored to standalone file 74 | 75 | # Carthage - Refactored to standalone file 76 | 77 | # fastlane 78 | # 79 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 80 | # screenshots whenever they are needed. 81 | # For more information about the recommended setup visit: 82 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 83 | 84 | fastlane/report.xml 85 | fastlane/Preview.html 86 | fastlane/screenshots 87 | fastlane/test_output 88 | 89 | ### Swift.Carthage Stack ### 90 | # Carthage 91 | # 92 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 93 | # Carthage/Checkouts 94 | 95 | Carthage/Build 96 | 97 | ### Swift.CocoaPods Stack ### 98 | ## CocoaPods GitIgnore Template 99 | 100 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 101 | # - Also handy if you have a lage number of dependant pods 102 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGONRE THE LOCK FILE 103 | Pods/ 104 | 105 | # End of https://www.gitignore.io/api/macos,swift -------------------------------------------------------------------------------- /LPAlbumDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // LPAlbumDemo 4 | // 5 | // Created by 郜宇 on 2017/9/8. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import LPAlbum 11 | 12 | class ViewController: UIViewController { 13 | 14 | fileprivate var collectionView: UICollectionView! 15 | fileprivate var photos = [UIImage]() 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | title = "LPAlbum" 20 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(openAlbum)) 21 | navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(clean)) 22 | 23 | let layout = UICollectionViewFlowLayout() 24 | let wh = view.bounds.width / 4.0 25 | layout.itemSize = CGSize(width: wh, height: wh) 26 | layout.minimumLineSpacing = 0 27 | layout.minimumInteritemSpacing = 0 28 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) 29 | collectionView.delegate = self 30 | collectionView.dataSource = self 31 | collectionView.backgroundColor = .white 32 | view.addSubview(collectionView) 33 | collectionView.register(PhotoCell.self, forCellWithReuseIdentifier: PhotoCell.description()) 34 | } 35 | 36 | @objc func openAlbum() { 37 | LPAlbum.show(at: self) { 38 | $0.columnCount = 4 39 | $0.hasCamera = true 40 | $0.maxSelectCount = 9 - self.photos.count 41 | }.targeSize({ (size) -> CGSize in 42 | return CGSize(width: 240, height: 240) 43 | }).error {(vc, error) in 44 | vc.show(message: error.localizedDescription) 45 | }.complete { [weak self](images) in 46 | self?.photos.append(contentsOf: images) 47 | self?.collectionView.reloadData() 48 | _ = images.map{ print($0.size) } 49 | } 50 | } 51 | @objc func clean() { 52 | photos.removeAll() 53 | collectionView.reloadData() 54 | } 55 | } 56 | 57 | extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate { 58 | 59 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 60 | return photos.count 61 | } 62 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 63 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoCell.description(), for: indexPath) as! PhotoCell 64 | cell.photoView.image = photos[indexPath.row] 65 | return cell 66 | } 67 | } 68 | 69 | 70 | extension UIViewController { 71 | func show(message: String){ 72 | let controler = UIAlertController(title: "提示", message: message, preferredStyle: .alert) 73 | controler.addAction(UIAlertAction(title: "确定", style: .cancel, handler: nil)) 74 | self.present(controler, animated: true, completion: nil) 75 | } 76 | } 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /LPAlbumDemo/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 | 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 | -------------------------------------------------------------------------------- /LPAlbum/Others/AlbumManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumManager.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/11. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Photos 11 | 12 | public class AlbumManager { 13 | 14 | static let imageManager = PHCachingImageManager() 15 | 16 | public class func getAlbums() -> [AlbumModel] { 17 | // 获取智能相册 18 | let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options: nil) 19 | // 获取用户创建的相册 20 | let userAlbums = PHAssetCollection.fetchTopLevelUserCollections(with: nil) 21 | // 遍历相册 22 | let smartModels = enumerateAlbum(album: smartAlbums as! PHFetchResult) 23 | let userModels = enumerateAlbum(album: userAlbums) 24 | // 数量排序 25 | return (smartModels + userModels).sorted{ $0.count > $1.count } 26 | } 27 | 28 | // 根据Asset获取photo 29 | @discardableResult 30 | public class func getPhoto(asset: PHAsset, 31 | targetSize: CGSize, 32 | adaptScale: Bool = true, 33 | option: PHImageRequestOptions? = nil, 34 | contentMode: PHImageContentMode = .aspectFill, 35 | resultHandler: @escaping ((UIImage?, [AnyHashable: Any]?) -> Void)) 36 | -> PHImageRequestID { 37 | 38 | let defalutOption = PHImageRequestOptions() 39 | defalutOption.resizeMode = .fast 40 | defalutOption.deliveryMode = .opportunistic 41 | 42 | let adaptSize = CGSize(width: targetSize.width * UIScreen.main.scale, 43 | height: targetSize.height * UIScreen.main.scale) 44 | return AlbumManager.imageManager.requestImage(for: asset, 45 | targetSize: adaptScale ? adaptSize : targetSize, 46 | contentMode: contentMode, 47 | options: option ?? defalutOption, 48 | resultHandler: resultHandler) 49 | } 50 | 51 | private class func enumerateAlbum(album: PHFetchResult) -> [AlbumModel] { 52 | 53 | var result = [AlbumModel]() 54 | 55 | let options = PHFetchOptions() 56 | options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] 57 | options.predicate = NSPredicate(format: "mediaType in %@", [PHAssetMediaType.image.rawValue]) 58 | 59 | album.enumerateObjects(options: .concurrent) { (collection, index, stop) in 60 | let assets = PHAsset.fetchAssets(in: collection as! PHAssetCollection, options: options) 61 | if assets.count != 0 && (collection.localizedTitle?.isAvailableAlbum ?? false){ 62 | let model = AlbumModel(name: collection.localizedTitle!, 63 | cover: assets[0], 64 | count: assets.count, 65 | assetModels: assets.toAssetModels) 66 | result.append(model) 67 | } 68 | } 69 | return result 70 | } 71 | 72 | } 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /LPAlbum/Views/PhotoPreviewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoPreviewCell.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/14. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PhotoPreviewCell: UICollectionViewCell { 12 | 13 | var assetModel: AssetModel! { 14 | didSet{ 15 | AlbumManager.getPhoto(asset: assetModel.asset, targetSize: bounds.size) { (image, _) in 16 | self.photoView.image = image 17 | let size = self.scrollView.frame.size 18 | let newSize = (image?.size ?? size).constrainedSize(toSize: size) 19 | self.photoView.bounds = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height) 20 | } 21 | } 22 | } 23 | func resetZoomScale() { scrollView.setZoomScale(1.0, animated: false) } 24 | var singleTapAction: (() -> Void)? 25 | 26 | fileprivate let photoView = UIImageView() 27 | fileprivate let scrollView = UIScrollView() 28 | 29 | override init(frame: CGRect) { 30 | super.init(frame: frame) 31 | 32 | scrollView.frame = bounds.insetBy(dx: 10, dy: 0) 33 | scrollView.minimumZoomScale = 1 34 | scrollView.maximumZoomScale = 3 35 | scrollView.bouncesZoom = true 36 | scrollView.delaysContentTouches = true 37 | scrollView.canCancelContentTouches = true 38 | scrollView.delegate = self 39 | scrollView.showsVerticalScrollIndicator = false 40 | scrollView.showsHorizontalScrollIndicator = false 41 | 42 | photoView.contentMode = .scaleAspectFit 43 | photoView.frame = scrollView.bounds 44 | scrollView.addSubview(photoView) 45 | contentView.addSubview(scrollView) 46 | 47 | let singleTap = UITapGestureRecognizer(target: self, action: #selector(singleTapClick)) 48 | let doubleTap = UITapGestureRecognizer(target: self, action: #selector(doubleTapClick)) 49 | doubleTap.numberOfTapsRequired = 2 50 | singleTap.require(toFail: doubleTap) 51 | contentView.addGestureRecognizer(singleTap) 52 | contentView.addGestureRecognizer(doubleTap) 53 | 54 | } 55 | 56 | @objc func singleTapClick() { singleTapAction?() } 57 | 58 | @objc func doubleTapClick(ges: UITapGestureRecognizer) { 59 | if scrollView.zoomScale > 1.0 { 60 | scrollView.contentInset = .zero 61 | scrollView.setZoomScale(1.0, animated: true) 62 | }else{ 63 | let point = ges.location(in: photoView) 64 | let zoonScale = scrollView.maximumZoomScale 65 | let xSize = bounds.size.width / zoonScale 66 | let ySize = bounds.size.height / zoonScale 67 | scrollView.zoom(to: CGRect(x: point.x - xSize/2, y: point.y - ySize/2, width: xSize, height: ySize), animated: true) 68 | } 69 | } 70 | 71 | required init?(coder aDecoder: NSCoder) { 72 | fatalError("init(coder:) has not been implemented") 73 | } 74 | } 75 | 76 | extension PhotoPreviewCell: UIScrollViewDelegate { 77 | func viewForZooming(in scrollView: UIScrollView) -> UIView? { 78 | return photoView 79 | } 80 | 81 | func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) { 82 | scrollView.contentInset = .zero 83 | } 84 | 85 | func scrollViewDidZoom(_ scrollView: UIScrollView) { 86 | let scrollViewW = scrollView.frame.width 87 | let scrollViewH = scrollView.frame.height 88 | let contentSizeW = scrollView.contentSize.width 89 | let contentSizeH = scrollView.contentSize.height 90 | // 缩小 91 | let offsetX = scrollViewW > contentSizeW ? (scrollViewW - contentSizeW) * 0.5 : 0 92 | let offsetY = scrollViewH > contentSizeH ? (scrollViewH - contentSizeH) * 0.5 : 0 93 | photoView.center = CGPoint(x: contentSizeW * 0.5 + offsetX, y: contentSizeH * 0.5 + offsetY) 94 | } 95 | 96 | } 97 | 98 | 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LPAlbum 2 | 3 | LPAlbum 4 | 5 | ![Xcode 8.3+](https://img.shields.io/badge/Xcode-8.3%2B-blue.svg) 6 | ![iOS 8.0+](https://img.shields.io/badge/iOS-8.0%2B-blue.svg) 7 | ![Swift 3.1+](https://img.shields.io/badge/Swift-3.0%2B-orange.svg) 8 | 9 | 10 | 11 | 12 | 13 | 14 | **LPAlbum** is a simple photo album 15 | 16 | 17 | ## Overview 18 | 19 | **LPAlbum** is a album including the function of multiple images, photo browsing, camera taking pictures. It is very easy to use 20 | 21 | 22 | 23 | 26 | 29 | 30 |
24 | 25 | 27 | 28 |
31 | 32 | ## How to use 33 | 34 | 1. Config 35 | 36 | 37 | ```Swift 38 | 39 | public extension LPAlbum { 40 | public struct Config { 41 | /// 最大选择数量 42 | public var maxSelectCount: Int = 6 43 | /// 每列照片数量 44 | public var columnCount: Int = 4 45 | /// 照片间距 46 | public var photoPadding: CGFloat = 2.0 47 | /// 是否有相机 48 | public var hasCamera: Bool = true 49 | } 50 | 51 | public struct Style { 52 | /// `NavigationBar`标题颜色 53 | public static var barTitleColor: UIColor = UIColor.white 54 | /// `NavigationBar`背景颜色 55 | public static var barTintColor: UIColor = UIColor.darkGray 56 | /// `NavigationBar`item文本颜色 57 | public static var tintColor: UIColor = UIColor.white 58 | /// 状态栏样式 59 | public static var statusBarStyle: UIStatusBarStyle = .lightContent 60 | /// 下拉箭头图片 61 | public static var arrowImage: UIImage = Bundle.imageFromBundle("meun_down")! 62 | /// 正常的选择框图片 63 | public static var normalBox: UIImage = Bundle.imageFromBundle("circle_normal")! 64 | /// 选中的选择框图片 65 | public static var selectedBox: UIImage = Bundle.imageFromBundle("circle_selected")! 66 | /// 选择框box的可点击区域向外的扩展size 67 | public static var boxEdgeInsets = UIEdgeInsets(top: -5, left: -5, bottom: -5, right: -5) 68 | } 69 | } 70 | 71 | ``` 72 | 73 | 74 | 2. Error: 75 | 76 | 77 | ```Swift 78 | 79 | public enum AlbumError: Error { 80 | case noAlbumPermission 81 | case noCameraPermission 82 | case moreThanLargestChoiceCount 83 | case savePhotoError 84 | 85 | public var localizedDescription: String { 86 | switch self { 87 | case .noAlbumPermission: return String.local("没有相册访问权限") 88 | case .noCameraPermission: return String.local("没有摄像头访问权限") 89 | case .moreThanLargestChoiceCount: return String.local("达到了图片选择最大数量") 90 | case .savePhotoError: return String.local("保存图片失败") 91 | } 92 | } 93 | } 94 | 95 | ``` 96 | 97 | 98 | 3. Use: 99 | 100 | > Tips: add row in your project info.plist 101 | > 1. `Privacy - Photo Library Usage Description` : `Can I get photo album permissions?` 102 | > 2. `Privacy - Camera Usage Description` : `Can I get camera permissions?` 103 | > 3. `Localization native development region` : `en` or `china` 104 | 105 | ```Swift 106 | 107 | // you can setup style yourself 108 | LPAlbum.Style.barTintColor = .gray 109 | LPAlbum.Style.tintColor = .white 110 | 111 | // show 112 | LPAlbum.show(at: self) { 113 | $0.columnCount = 4 114 | $0.hasCamera = true 115 | $0.maxSelectCount = 9 - self.photos.count 116 | }.targeSize({ (size) -> CGSize in 117 | return CGSize(width: 240, height: 240) 118 | }).error {(vc, error) in 119 | vc.show(message: error.localizedDescription) 120 | }.complete { [weak self](images) in 121 | self?.photos.append(contentsOf: images) 122 | self?.collectionView.reloadData() 123 | _ = images.map{ print($0.size) } 124 | } 125 | 126 | ``` 127 | 128 | 129 | ## Features 130 | 131 | - [ ] Support for iCloud 132 | - [ ] Support for cutting 133 | - [ ] Support for selecting a single image 134 | - [ ] Support for choosing the original image 135 | - [ ] Add preview of the selected photos 136 | 137 | 138 | ## Installation 139 | 140 | 141 | ### 1. CocoaPods 142 | 143 | [CocoaPods](https://cocoapods.org/) is a dependency manager for Cocoa projects. 144 | 145 | Specify LPAlbum into your project's Podfile: 146 | 147 | 148 | ```ruby 149 | # source 'https://github.com/CocoaPods/Specs.git' 150 | 151 | platform :ios, '9.0' 152 | use_frameworks! 153 | 154 | target '' do 155 | pod 'LPAlbum', '~> 0.0.9' 156 | end 157 | ``` 158 | 159 | 160 | Then run the following command: 161 | 162 | ```sh 163 | $ pod install 164 | ``` 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /LPAlbum/Controllers/PhotosBrowerController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumDetailController.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/12. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PhotosBrowerController: UIViewController { 12 | 13 | var assetModels: [AssetModel]! 14 | var currentIndex: Int! 15 | 16 | var chooseAction: ((Int, UIButton, PhotosBrowerController) -> Void)? 17 | 18 | fileprivate var collectionView: UICollectionView! 19 | fileprivate let itemPadding: CGFloat = 20.0 20 | fileprivate let chooseButton = UIButton() 21 | fileprivate var itemSize: CGSize! 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | setupUI() 26 | refreshNavigation() 27 | // addCache() 28 | } 29 | 30 | deinit { 31 | print("\(self) deinit") 32 | // removeCache() 33 | } 34 | } 35 | 36 | extension PhotosBrowerController { 37 | 38 | func setupUI(){ 39 | // 将collectionView多余的部分切到 40 | view.clipsToBounds = true 41 | automaticallyAdjustsScrollViewInsets = false 42 | chooseButton.frame = CGRect(x: 0, y: 0, width: 25, height: 25) 43 | chooseButton.setBackgroundImage(LPAlbum.Style.selectedBox, for: .selected) 44 | chooseButton.setBackgroundImage(LPAlbum.Style.normalBox, for: .normal) 45 | chooseButton.addTarget(self, action: #selector(chooseClick), for: .touchUpInside) 46 | navigationItem.rightBarButtonItem = UIBarButtonItem(customView: chooseButton) 47 | 48 | let layout = UICollectionViewFlowLayout() 49 | itemSize = CGSize(width: view.bounds.width + 20, height: view.bounds.height) 50 | layout.itemSize = itemSize 51 | layout.minimumLineSpacing = 0 52 | layout.minimumInteritemSpacing = 0 53 | layout.scrollDirection = .horizontal 54 | collectionView = UICollectionView(frame: CGRect(x: -10, y: 0, width: itemSize.width, height: itemSize.height), 55 | collectionViewLayout: layout) 56 | collectionView.isPagingEnabled = true 57 | collectionView.showsHorizontalScrollIndicator = false 58 | collectionView.delegate = self 59 | collectionView.dataSource = self 60 | view.addSubview(collectionView) 61 | collectionView.register(PhotoPreviewCell.self, forCellWithReuseIdentifier: PhotoPreviewCell.description()) 62 | collectionView.performBatchUpdates(nil){ _ in 63 | self.collectionView.setContentOffset(CGPoint(x: self.itemSize.width * self.currentIndex.cgFloat, y: 0), animated: false) 64 | } 65 | } 66 | 67 | func refreshNavigation(){ 68 | chooseButton.isSelected = assetModels[currentIndex].isSelect 69 | title = (currentIndex + 1).description + "/" + assetModels.count.description 70 | } 71 | 72 | func addCache() { 73 | let scale = UIScreen.main.scale 74 | DispatchQueue.global().async { 75 | AlbumManager.imageManager.startCachingImages(for: self.assetModels.map{ $0.asset }, 76 | targetSize: CGSize(width: self.itemSize.width * scale, height: self.itemSize.height * scale), 77 | contentMode: .aspectFill, 78 | options: nil) 79 | } 80 | } 81 | func removeCache() { 82 | let scale = UIScreen.main.scale 83 | AlbumManager.imageManager.stopCachingImages(for: assetModels.map{ $0.asset }, 84 | targetSize: CGSize(width: itemSize.width * scale, height: itemSize.height * scale), 85 | contentMode: .aspectFill, 86 | options: nil) 87 | } 88 | 89 | @objc func chooseClick() { 90 | chooseAction?(currentIndex, chooseButton, self) 91 | } 92 | } 93 | 94 | extension PhotosBrowerController: UICollectionViewDelegate, UICollectionViewDataSource { 95 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 96 | return assetModels.count 97 | } 98 | 99 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 100 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoPreviewCell.description(), for: indexPath) as! PhotoPreviewCell 101 | cell.assetModel = assetModels[indexPath.row] 102 | cell.singleTapAction = { [weak self] in 103 | guard let `self` = self else { return } 104 | let isHidden = self.navigationController?.isNavigationBarHidden ?? false 105 | self.navigationController?.setNavigationBarHidden(!isHidden, animated: true) 106 | } 107 | return cell 108 | } 109 | func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 110 | (cell as? PhotoPreviewCell)?.resetZoomScale() 111 | } 112 | 113 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 114 | currentIndex = (scrollView.contentOffset.x / scrollView.bounds.width).roundInt 115 | refreshNavigation() 116 | } 117 | } 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /LPAlbum/Views/DropMenuView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropMenuView.swift 3 | // MaiDou 4 | // 5 | // Created by 郜宇 on 2017/8/22. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class DropMenuView: UIView { 13 | 14 | fileprivate let tableView = UITableView() 15 | fileprivate let backgroundView = UIView() 16 | fileprivate var albums = [AlbumModel]() 17 | 18 | var maskViewClick: ((DropMenuView) -> Void)? 19 | var menuViewDidSelect: ((Int) -> Void)? 20 | 21 | func show(begin: (() -> Void)? = nil, complete: (() -> Void)? = nil) { 22 | begin?() 23 | alpha = 1.0 24 | UIView.animate(withDuration: 0.35, animations: { 25 | self.tableView.transform = CGAffineTransform(translationX: 0, y: self.tableView.bounds.height) 26 | self.backgroundView.alpha = 1.0 27 | }) { (_) in 28 | complete?() 29 | } 30 | } 31 | 32 | func dismiss(begin: (() -> Void)? = nil, complete: (() -> Void)? = nil) { 33 | begin?() 34 | UIView.animate(withDuration: 0.35, animations: { 35 | self.tableView.transform = .identity 36 | self.backgroundView.alpha = 0.0 37 | }) { (_) in 38 | self.alpha = 0 39 | complete?() 40 | } 41 | } 42 | 43 | init(frame: CGRect, albums: [AlbumModel]) { 44 | super.init(frame: frame) 45 | 46 | let rowHeight: CGFloat = 65 47 | 48 | self.albums = albums 49 | tableView.delegate = self 50 | tableView.dataSource = self 51 | alpha = 0 52 | 53 | backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.5) 54 | backgroundView.alpha = 0 55 | let tap = UITapGestureRecognizer(target: self, action: #selector(maskClick)) 56 | backgroundView.addGestureRecognizer(tap) 57 | 58 | addSubview(backgroundView) 59 | addSubview(tableView) 60 | 61 | tableView.register(MenuCell.self, forCellReuseIdentifier: MenuCell.description()) 62 | tableView.separatorColor = UIColor.groupTableViewBackground 63 | tableView.separatorInset = .zero 64 | tableView.rowHeight = rowHeight 65 | 66 | let height = min(rowHeight * CGFloat(albums.count), 400) 67 | 68 | tableView.translatesAutoresizingMaskIntoConstraints = false 69 | tableView.topAnchor.constraint(equalTo: topAnchor, constant: -height).isActive = true 70 | tableView.heightAnchor.constraint(equalToConstant: height).isActive = true 71 | tableView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true 72 | tableView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true 73 | 74 | backgroundView.translatesAutoresizingMaskIntoConstraints = false 75 | let left = backgroundView.leftAnchor.constraint(equalTo: leftAnchor) 76 | let right = backgroundView.rightAnchor.constraint(equalTo: rightAnchor) 77 | let top = backgroundView.topAnchor.constraint(equalTo: topAnchor) 78 | let bottom = backgroundView.bottomAnchor.constraint(equalTo: bottomAnchor) 79 | NSLayoutConstraint.activate([left, right, top, bottom]) 80 | } 81 | 82 | @objc private func maskClick() { dismiss(); maskViewClick?(self) } 83 | 84 | required init?(coder aDecoder: NSCoder) { 85 | fatalError("init(coder:) has not been implemented") 86 | } 87 | } 88 | 89 | extension DropMenuView: UITableViewDelegate, UITableViewDataSource { 90 | 91 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 92 | return albums.count 93 | } 94 | 95 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 96 | let cell = tableView.dequeueReusableCell(withIdentifier: MenuCell.description(), for: indexPath) as! MenuCell 97 | cell.album = albums[indexPath.row] 98 | return cell 99 | } 100 | 101 | func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 102 | (cell as! MenuCell).album = albums[indexPath.row] 103 | } 104 | 105 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 106 | dismiss() 107 | menuViewDidSelect?(indexPath.row) 108 | } 109 | } 110 | 111 | 112 | final class MenuCell: UITableViewCell { 113 | 114 | private let titleLabel = UILabel() 115 | private let iconView = UIImageView() 116 | 117 | var album: AlbumModel! { 118 | didSet{ 119 | titleLabel.text = album.name + " \(album.count)" 120 | AlbumManager.getPhoto(asset: album.cover, targetSize: iconView.frame.size) {[weak self] (image, _) in 121 | self?.iconView.image = image 122 | } 123 | layoutIfNeeded() 124 | } 125 | } 126 | 127 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 128 | super.init(style: style, reuseIdentifier: reuseIdentifier) 129 | 130 | titleLabel.textColor = UIColor.black 131 | titleLabel.font = UIFont.systemFont(ofSize: 17) 132 | titleLabel.lineBreakMode = .byTruncatingMiddle 133 | 134 | iconView.clipsToBounds = true 135 | iconView.contentMode = .scaleAspectFill 136 | 137 | contentView.addSubview(titleLabel) 138 | contentView.addSubview(iconView) 139 | } 140 | 141 | override func layoutSubviews() { 142 | super.layoutSubviews() 143 | let padding: CGFloat = 10.0 144 | titleLabel.sizeToFit() 145 | titleLabel.bounds = CGRect(x: 0, y: 0, 146 | width: min(bounds.width - iconView.frame.maxX - 2 * padding, titleLabel.frame.width), 147 | height: titleLabel.frame.height) 148 | iconView.bounds = CGRect(x: 0, y: 0, width: 55, height: 55) 149 | iconView.center = CGPoint(x: 40, y: bounds.height * 0.5) 150 | titleLabel.center = CGPoint(x: titleLabel.frame.width * 0.5 + iconView.frame.maxX + padding, y: bounds.height * 0.5) 151 | } 152 | 153 | 154 | required init?(coder aDecoder: NSCoder) { 155 | fatalError("init(coder:) has not been implemented") 156 | } 157 | } 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /LPAlbum/Others/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/12. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension CGFloat { 12 | static var screenWidth = UIScreen.main.bounds.width 13 | static var screenHeight = UIScreen.main.bounds.height 14 | 15 | var roundInt: Int { return Int(self.rounded()) } 16 | } 17 | 18 | extension Int { 19 | var cgFloat: CGFloat { return CGFloat(self) } 20 | var half: CGFloat { return self.cgFloat / 2 } 21 | var adapt: CGFloat { return self.cgFloat * CGFloat.screenWidth / 375.0 } 22 | } 23 | 24 | extension Bundle { 25 | // 资源的bundle 26 | static var albumBundle: Bundle { 27 | let bundle = Bundle(for: LPAlbum.self) 28 | return Bundle(path: bundle.path(forResource: "LPAlbum", ofType: "bundle")!)! 29 | } 30 | 31 | static func imageFromBundle(_ imageName: String) -> UIImage? { 32 | var imageName = imageName 33 | if UIScreen.main.scale == 2 { 34 | imageName = imageName + "@2x" 35 | }else if UIScreen.main.scale == 3 { 36 | imageName = imageName + "@3x" 37 | } 38 | guard let bundle = Bundle(path: albumBundle.bundlePath + "/Images"), 39 | let path = bundle.path(forResource: imageName, ofType: "png") else { return nil } 40 | return UIImage(contentsOfFile: path) 41 | } 42 | 43 | static func localizedString(key: String) -> String { 44 | guard let code = Locale.current.languageCode else { return key } 45 | var language = "zh" 46 | if code == "en" { language = "en" } 47 | guard let path = albumBundle.path(forResource: language, ofType: "lproj"), 48 | let bundle = Bundle(path: path) else { return key } 49 | return bundle.localizedString(forKey: key, value: nil, table: nil) 50 | } 51 | } 52 | 53 | 54 | 55 | extension UIImage { 56 | 57 | func scaleImage(to size: CGSize, 58 | contentMode: UIViewContentMode, 59 | corner: CGFloat = 0, 60 | backGroundColor: UIColor = UIColor.clear) -> UIImage? 61 | { 62 | UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) 63 | 64 | var newSize = self.size 65 | var newImageFrame = CGRect(x: 0, y: 0, width: size.width, height: size.height) 66 | 67 | switch contentMode { 68 | case .scaleAspectFit: 69 | newSize = self.size.constrainedSize(toSize: size) 70 | if newSize.width == size.width { 71 | newImageFrame = CGRect(x: 0, 72 | y: (size.height - newSize.height)/2, 73 | width: newSize.width, 74 | height: newSize.height) 75 | } 76 | if newSize.height == size.height { 77 | newImageFrame = CGRect(x: 0, 78 | y: (size.width - newSize.width)/2, 79 | width: newSize.width, 80 | height: newSize.height) 81 | } 82 | case .scaleAspectFill: 83 | newSize = self.size.fillingSize(toSize: size) 84 | if newSize.width > size.width { 85 | newImageFrame = CGRect(x: -(newSize.width - size.width)/2, 86 | y: 0, 87 | width: newSize.width, 88 | height: newSize.height) 89 | } 90 | if newSize.height > size.height { 91 | newImageFrame = CGRect(x: 0, 92 | y: -(newSize.height - size.height)/2, 93 | width: newSize.width, 94 | height: newSize.height) 95 | } 96 | default: break 97 | } 98 | backGroundColor.set() 99 | UIRectFill(newImageFrame) 100 | UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: corner).addClip() 101 | self.draw(in: newImageFrame) 102 | let result = UIGraphicsGetImageFromCurrentImageContext() 103 | UIGraphicsEndImageContext() 104 | return result 105 | } 106 | } 107 | 108 | extension CGSize { 109 | func fillingSize(toSize: CGSize) -> CGSize { 110 | let aspectWidth = round(aspectRatio * toSize.height) 111 | let aspectHeight = round(toSize.width / aspectRatio) 112 | return aspectWidth < toSize.width ? 113 | CGSize(width: toSize.width, height: aspectHeight) : 114 | CGSize(width: aspectWidth, height: toSize.height) 115 | } 116 | func constrainedSize(toSize: CGSize) -> CGSize { 117 | let aspectWidth = round(aspectRatio * toSize.height) 118 | let aspectHeight = round(toSize.width / aspectRatio) 119 | return aspectWidth > toSize.width ? 120 | CGSize(width: toSize.width, height: aspectHeight) : 121 | CGSize(width: aspectWidth, height: toSize.height) 122 | } 123 | private var aspectRatio: CGFloat { 124 | return height == 0.0 ? 1.0 : width / height 125 | } 126 | } 127 | 128 | 129 | var keyHitThstEdgeInsets = "HitTestEdgeInsets" 130 | extension UIControl { 131 | 132 | var lp_hitEdgeInsets: UIEdgeInsets? { 133 | get { 134 | let value = objc_getAssociatedObject(self, &keyHitThstEdgeInsets) as? NSValue 135 | return value.map { $0.uiEdgeInsetsValue } 136 | } 137 | set { 138 | guard newValue != nil else { return } 139 | objc_setAssociatedObject(self, &keyHitThstEdgeInsets, NSValue(uiEdgeInsets: newValue!), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 140 | } 141 | } 142 | 143 | open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { 144 | if self.lp_hitEdgeInsets == nil || !self.isEnabled || self.isHidden { 145 | return super.point(inside: point, with: event) 146 | } 147 | 148 | let relativeFrame = self.bounds 149 | let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.lp_hitEdgeInsets!) 150 | return hitFrame.contains(point) 151 | } 152 | } 153 | 154 | extension String { 155 | /// 是否是有效的相册 156 | var isAvailableAlbum: Bool { 157 | return !(isEmpty || contains("Hidden") || contains("已隐藏") || contains("Deleted") || contains("最近删除")) 158 | } 159 | /// 国际化文本 160 | static func local(_ key: String) -> String { 161 | return Bundle.localizedString(key: key) 162 | } 163 | } 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /LPAlbum/Controllers/LPAlbum.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LPAlbum.swift 3 | // LPAlbum 4 | // 5 | // Created by 郜宇 on 2017/9/8. 6 | // Copyright © 2017年 Loopeer. All rights reserved. 7 | /** 8 | TODO: 1. add CompleteSelectAssetsBlock 可以选择原图 9 | 2. 选取单个图片的情形 10 | 3. 支持裁剪(正方形, 圆形) 11 | 4. 是否要加Observer, 如果有Observer, 图片添加或者删除了, 要响应的添加或删除该图片的缓存 12 | 5. 加一些动画效果 13 | 6. 是否内部对图片处理画一遍 14 | */ 15 | 16 | import UIKit 17 | import Photos 18 | 19 | public typealias SetConfigBlock = ((inout LPAlbum.Config) -> Void) 20 | public typealias CompleteSelectImagesBlock = (([UIImage]) -> Void) 21 | public typealias CompleteSelectAssetsBlock = (([PHAsset]) -> Void) 22 | public typealias ErrorBlock = ((UIViewController ,AlbumError) -> Void) 23 | public typealias TargetSizeBlock = ((CGSize) -> CGSize) 24 | 25 | 26 | public class LPAlbum: UIViewController { 27 | 28 | fileprivate let config: Config 29 | fileprivate var completeSelectImagesBlock: CompleteSelectImagesBlock? 30 | fileprivate var completeSelectAssetsBlock: CompleteSelectAssetsBlock? 31 | 32 | fileprivate var errorBlock: ErrorBlock? 33 | fileprivate var targetSizeBlock: TargetSizeBlock? 34 | 35 | fileprivate var collectionView: UICollectionView! 36 | fileprivate var titleView: TitleView! 37 | fileprivate var menuView: DropMenuView! 38 | fileprivate var confirmItem: UIBarButtonItem! 39 | 40 | fileprivate var currentAlbumIndex: Int = 0 41 | fileprivate var albumModels = [AlbumModel]() 42 | fileprivate var selectedAssets = [PHAsset]() 43 | fileprivate var allAssets = [PHAsset]() { 44 | didSet{ 45 | //如果有Observer, 图片添加或者删除了, 要响应的添加或删除该图片的缓存 46 | let itemSize = (self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout).itemSize 47 | let scale = UIScreen.main.scale 48 | let targetSize = CGSize(width: itemSize.width * scale, height: itemSize.height * scale) 49 | DispatchQueue.global().async { 50 | AlbumManager.imageManager.startCachingImages(for: self.allAssets, targetSize: targetSize, contentMode: .aspectFill, options: nil) 51 | } 52 | } 53 | } 54 | 55 | @discardableResult 56 | public class func show(at: UIViewController, set: SetConfigBlock? = nil) -> LPAlbum { 57 | var c = Config(); set?(&c); 58 | let vc = LPAlbum(c) 59 | // 异步延后调用, 使`errorBlock`不为空 60 | DispatchQueue.main.async { 61 | let nav = LPNavigationController(rootViewController: vc) 62 | AuthorizationTool.albumRRequestAuthorization { 63 | $0 == .authorized ? at.present(nav, animated: true, completion: nil) : vc.errorBlock?(at, AlbumError.noAlbumPermission) 64 | } 65 | } 66 | return vc 67 | } 68 | 69 | @discardableResult 70 | public func targeSize(_ map: @escaping TargetSizeBlock) -> Self{ 71 | targetSizeBlock = map 72 | return self 73 | } 74 | 75 | @discardableResult 76 | public func complete(_ complete: @escaping CompleteSelectImagesBlock) -> Self { 77 | completeSelectImagesBlock = complete 78 | return self 79 | } 80 | 81 | @discardableResult 82 | public func error(_ error: @escaping ErrorBlock) -> Self { 83 | errorBlock = error 84 | return self 85 | } 86 | 87 | init(_ config: Config) { 88 | self.config = config 89 | super.init(nibName: nil, bundle: nil) 90 | } 91 | 92 | required public init?(coder aDecoder: NSCoder) { 93 | fatalError("init(coder:) has not been implemented") 94 | } 95 | 96 | public override func viewDidLoad() { 97 | super.viewDidLoad() 98 | 99 | albumModels = AlbumManager.getAlbums() 100 | setupUI() 101 | allAssets = albumModels[0].assetModels.map{ $0.asset } 102 | 103 | titleView.clickAction = { [weak self] in 104 | $0.isSelected = !$0.isSelected 105 | $0.isSelected ? 106 | self?.menuView.show(begin: { 107 | self?.titleView.isEnabled = false 108 | }, complete: { 109 | self?.titleView.isEnabled = true 110 | }) : 111 | self?.menuView.dismiss(begin: { 112 | self?.titleView.isEnabled = false 113 | }, complete: { 114 | self?.titleView.isEnabled = true 115 | }) 116 | } 117 | menuView.maskViewClick = {[weak self] _ in 118 | self?.titleView.isSelected = false 119 | } 120 | menuView.menuViewDidSelect = {[weak self] in 121 | self?.titleView.isSelected = false 122 | self?.currentAlbumIndex = $0 123 | self?.titleView.title = self?.albumModels[$0].name 124 | self?.collectionView.reloadData() 125 | } 126 | } 127 | 128 | deinit { 129 | print("\(self) deinit") 130 | } 131 | } 132 | 133 | 134 | extension LPAlbum { 135 | 136 | func setupUI() { 137 | navigationItem.leftBarButtonItem = UIBarButtonItem(title: String.local("取消"), style: .plain, target: self, action: #selector(cancel)) 138 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: String.local("完成"), style: .plain, target: self, action: #selector(confirm)) 139 | navigationItem.rightBarButtonItem!.isEnabled = false 140 | confirmItem = navigationItem.rightBarButtonItem! 141 | 142 | titleView = TitleView(frame: .zero) 143 | navigationItem.titleView = titleView 144 | titleView.title = albumModels[currentAlbumIndex].name 145 | 146 | let layout = UICollectionViewFlowLayout() 147 | let photoW = (view.bounds.width - config.photoPadding * CGFloat(config.columnCount - 1)) / CGFloat(config.columnCount) 148 | layout.itemSize = CGSize(width: photoW, height: photoW) 149 | layout.minimumLineSpacing = config.photoPadding 150 | layout.minimumInteritemSpacing = config.photoPadding 151 | collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height), 152 | collectionViewLayout: layout) 153 | collectionView.delegate = self 154 | collectionView.dataSource = self 155 | collectionView.backgroundColor = .white 156 | view.addSubview(collectionView) 157 | 158 | collectionView.register(AlbumCollectionCell.self, forCellWithReuseIdentifier: AlbumCollectionCell.description()) 159 | collectionView.register(TakeCameraCell.self, forCellWithReuseIdentifier: TakeCameraCell.description()) 160 | 161 | menuView = DropMenuView(frame: CGRect(x: 0, y: 88, width: .screenWidth, height: .screenHeight - 88), albums: albumModels) 162 | view.addSubview(menuView) 163 | menuView.translatesAutoresizingMaskIntoConstraints = false 164 | if #available(iOS 11, *) { 165 | menuView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true 166 | }else{ 167 | menuView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true 168 | } 169 | menuView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true 170 | menuView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true 171 | menuView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 172 | } 173 | 174 | @objc func cancel() { dismiss(animated: true, completion: nil) } 175 | @objc func confirm() { 176 | var result = [UIImage]() 177 | for asset in selectedAssets { 178 | let size = CGSize(width: asset.pixelWidth, height: asset.pixelHeight) //PHImageManagerMaximumSize 179 | let targetSize = targetSizeBlock?(size) ?? size 180 | let option = PHImageRequestOptions() 181 | option.isSynchronous = true 182 | option.resizeMode = .exact 183 | DispatchQueue.global().async { 184 | AlbumManager.getPhoto(asset: asset, 185 | targetSize: targetSize, 186 | adaptScale: false, 187 | option: option, 188 | contentMode: .aspectFit, 189 | resultHandler: {[weak self] (image, _) in 190 | if image != nil { result.append(image!) } 191 | if result.count == self?.selectedAssets.count { 192 | DispatchQueue.main.async { self?.completeSelectImagesBlock?(result) } 193 | } 194 | }) 195 | } 196 | cancel() 197 | } 198 | } 199 | 200 | func cellDidSelectForPreviewViewController(indexPath: IndexPath) -> UIViewController{ 201 | let previewVc = PhotosBrowerController() 202 | let assetModels = albumModels[currentAlbumIndex].assetModels 203 | previewVc.assetModels = assetModels 204 | previewVc.currentIndex = config.hasCamera ? indexPath.row - 1 : indexPath.row 205 | previewVc.chooseAction = {[weak self] (index, button, vc) in 206 | guard let `self` = self else { return } 207 | 208 | let willselect = !button.isSelected 209 | let asset = vc.assetModels[index].asset 210 | guard self.checkoutCount(willselect: willselect, asset: asset, show: vc) else { return } 211 | 212 | button.isSelected = willselect 213 | vc.assetModels[index].isSelect = willselect 214 | self.albumModels = self.albumModels.change(assetModel: vc.assetModels[index]) 215 | 216 | let cellIndex = self.config.hasCamera ? index + 1 : index 217 | self.collectionView.reloadItems(at: [IndexPath(row: cellIndex, section: 0)]) 218 | } 219 | return previewVc 220 | } 221 | } 222 | 223 | 224 | extension LPAlbum: UICollectionViewDelegate, UICollectionViewDataSource { 225 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 226 | 227 | if config.hasCamera && indexPath.row == 0 { 228 | return collectionView.dequeueReusableCell(withReuseIdentifier: TakeCameraCell.description(), for: indexPath) as! TakeCameraCell 229 | }else{ 230 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AlbumCollectionCell.description(), for: indexPath) as! AlbumCollectionCell 231 | 232 | let model = albumModels[currentAlbumIndex].assetModels[config.hasCamera ? indexPath.row - 1 : indexPath.row] 233 | cell.set(model) 234 | cell.iconClickAction = {[weak self] in 235 | guard let `self` = self else { return } 236 | guard self.checkoutCount(willselect: !$0, asset: model.asset, show: self) else { return } 237 | var newModel = model 238 | newModel.isSelect = !$0 239 | self.albumModels = self.albumModels.change(assetModel: newModel) 240 | UIView.performWithoutAnimation { self.collectionView.reloadItems(at: [indexPath]) } 241 | } 242 | if #available(iOS 9.0, *) { 243 | guard traitCollection.forceTouchCapability == .available else { return cell } 244 | registerForPreviewing(with: self, sourceView: cell) 245 | } 246 | return cell 247 | } 248 | } 249 | 250 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 251 | let photoCount = albumModels[currentAlbumIndex].assetModels.count 252 | return config.hasCamera ? photoCount + 1 : photoCount 253 | } 254 | 255 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 256 | if config.hasCamera && indexPath.row == 0 { 257 | guard checkoutCount(willselect: true, show: self) else { return } 258 | AuthorizationTool.cameraRequestAuthorization{ 259 | $0 == .authorized ? self.takePhoto() : self.errorBlock?(self, AlbumError.noCameraPermission) 260 | } 261 | }else{ 262 | let previewVc = cellDidSelectForPreviewViewController(indexPath: indexPath) 263 | navigationController?.pushViewController(previewVc, animated: true) 264 | } 265 | } 266 | 267 | func takePhoto() { 268 | let sourceType = UIImagePickerControllerSourceType.camera 269 | guard UIImagePickerController.isSourceTypeAvailable(sourceType) else { fatalError("摄像头不可用") } 270 | let picker = UIImagePickerController() 271 | picker.sourceType = sourceType 272 | picker.allowsEditing = false 273 | picker.delegate = self 274 | present(picker, animated: true, completion: nil) 275 | } 276 | 277 | func checkoutCount(willselect: Bool, asset: PHAsset? = nil, show vc: UIViewController) -> Bool { 278 | 279 | if self.config.maxSelectCount == self.selectedAssets.count && willselect { 280 | self.errorBlock?(vc,AlbumError.moreThanLargestChoiceCount) 281 | return false 282 | } 283 | guard let asset = asset else { return true } 284 | if willselect { self.selectedAssets.append(asset) } else { _ = self.selectedAssets.remove(asset) } 285 | confirmItem.isEnabled = self.selectedAssets.count > 0 286 | return true 287 | } 288 | } 289 | 290 | extension LPAlbum: UIImagePickerControllerDelegate, UINavigationControllerDelegate { 291 | public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { 292 | picker.dismiss(animated: true, completion: nil) 293 | } 294 | public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { 295 | 296 | guard let image = (info[UIImagePickerControllerEditedImage] as? UIImage) ?? ( info[UIImagePickerControllerOriginalImage] as? UIImage) else { 297 | picker.dismiss(animated: true, completion: nil); return; 298 | } 299 | 300 | PHPhotoLibrary.shared().performChanges({ 301 | PHAssetChangeRequest.creationRequestForAsset(from: image) 302 | }) { [weak self] (success, error) in 303 | guard let `self` = self else { return } 304 | if success == false || error != nil { self.errorBlock?(self, AlbumError.savePhotoError) } 305 | DispatchQueue.main.async { 306 | let targetSize = self.targetSizeBlock?(image.size) ?? image.size 307 | self.completeSelectImagesBlock?([image.scaleImage(to: targetSize, contentMode: .scaleAspectFill) ?? image]) 308 | picker.dismiss(animated: true, completion: nil) 309 | self.dismiss(animated: true, completion: nil) 310 | } 311 | } 312 | } 313 | } 314 | 315 | 316 | extension LPAlbum: UIViewControllerPreviewingDelegate { 317 | public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { 318 | 319 | guard let cell = previewingContext.sourceView as? AlbumCollectionCell, 320 | let indexPath = collectionView.indexPath(for: cell) else { return nil } 321 | let previewVc = PhotoPreviewController() 322 | previewVc.assetModel = albumModels[currentAlbumIndex].assetModels[config.hasCamera ? indexPath.row - 1 : indexPath.row] 323 | previewingContext.sourceRect = previewingContext.sourceView.bounds 324 | return previewVc 325 | } 326 | 327 | public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { 328 | guard let cell = previewingContext.sourceView as? AlbumCollectionCell, 329 | let indexPath = collectionView.indexPath(for: cell) else { return } 330 | let vc = cellDidSelectForPreviewViewController(indexPath: indexPath) 331 | navigationController?.pushViewController(vc, animated: true) 332 | } 333 | } 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | -------------------------------------------------------------------------------- /LPAlbum.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7319E28C1F7A645F0048B3C7 /* PhotoPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7319E28B1F7A645F0048B3C7 /* PhotoPreviewController.swift */; }; 11 | 731A48081F623AF000AC5513 /* LPAlbum.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 731A47FE1F623AF000AC5513 /* LPAlbum.framework */; }; 12 | 731A480D1F623AF000AC5513 /* LPAlbumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731A480C1F623AF000AC5513 /* LPAlbumTests.swift */; }; 13 | 731A480F1F623AF000AC5513 /* LPAlbum.h in Headers */ = {isa = PBXBuildFile; fileRef = 731A48011F623AF000AC5513 /* LPAlbum.h */; settings = {ATTRIBUTES = (Public, ); }; }; 14 | 731A481F1F623B3E00AC5513 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731A481E1F623B3E00AC5513 /* AppDelegate.swift */; }; 15 | 731A48211F623B3E00AC5513 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731A48201F623B3E00AC5513 /* ViewController.swift */; }; 16 | 731A48241F623B3E00AC5513 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 731A48221F623B3E00AC5513 /* Main.storyboard */; }; 17 | 731A48261F623B3E00AC5513 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 731A48251F623B3E00AC5513 /* Assets.xcassets */; }; 18 | 731A48291F623B3E00AC5513 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 731A48271F623B3E00AC5513 /* LaunchScreen.storyboard */; }; 19 | 7337B6741F6850C800F1FE84 /* PhotoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7337B6731F6850C800F1FE84 /* PhotoCell.swift */; }; 20 | 738C26041F67934D00040E4E /* PhotosBrowerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738C26031F67934D00040E4E /* PhotosBrowerController.swift */; }; 21 | 738C26061F67C94500040E4E /* TakeCameraCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738C26051F67C94500040E4E /* TakeCameraCell.swift */; }; 22 | 738C260B1F67E43200040E4E /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738C260A1F67E43200040E4E /* Extensions.swift */; }; 23 | 739180FB1F6677BE001EACFF /* LPAlbum.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 731A47FE1F623AF000AC5513 /* LPAlbum.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 24 | 739181091F669FC9001EACFF /* LPAlbum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739181071F669FC9001EACFF /* LPAlbum.swift */; }; 25 | 7391810A1F669FC9001EACFF /* LPNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739181081F669FC9001EACFF /* LPNavigationController.swift */; }; 26 | 739181121F669FCE001EACFF /* AlbumError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7391810C1F669FCE001EACFF /* AlbumError.swift */; }; 27 | 739181131F669FCE001EACFF /* AlbumManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7391810D1F669FCE001EACFF /* AlbumManager.swift */; }; 28 | 739181141F669FCE001EACFF /* AuthorizationTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7391810E1F669FCE001EACFF /* AuthorizationTool.swift */; }; 29 | 739181151F669FCE001EACFF /* LPAlbum.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 7391810F1F669FCE001EACFF /* LPAlbum.bundle */; }; 30 | 739181171F669FCE001EACFF /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739181111F669FCE001EACFF /* Models.swift */; }; 31 | 7391811A1F669FD2001EACFF /* AlbumCollectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739181191F669FD2001EACFF /* AlbumCollectionCell.swift */; }; 32 | 7391811D1F66A33C001EACFF /* DropMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7391811B1F66A33C001EACFF /* DropMenuView.swift */; }; 33 | 7391811F1F66B3A4001EACFF /* TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7391811C1F66A33C001EACFF /* TitleView.swift */; }; 34 | 73A85A881F68EDF000C3096E /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A85A871F68EDF000C3096E /* Config.swift */; }; 35 | 73BF7BF01F6A24E400A0BEC1 /* PhotoPreviewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73BF7BEF1F6A24E400A0BEC1 /* PhotoPreviewCell.swift */; }; 36 | /* End PBXBuildFile section */ 37 | 38 | /* Begin PBXContainerItemProxy section */ 39 | 731A48091F623AF000AC5513 /* PBXContainerItemProxy */ = { 40 | isa = PBXContainerItemProxy; 41 | containerPortal = 731A47F51F623AF000AC5513 /* Project object */; 42 | proxyType = 1; 43 | remoteGlobalIDString = 731A47FD1F623AF000AC5513; 44 | remoteInfo = LPAlbum; 45 | }; 46 | /* End PBXContainerItemProxy section */ 47 | 48 | /* Begin PBXCopyFilesBuildPhase section */ 49 | 739180FA1F667792001EACFF /* CopyFiles */ = { 50 | isa = PBXCopyFilesBuildPhase; 51 | buildActionMask = 2147483647; 52 | dstPath = ""; 53 | dstSubfolderSpec = 10; 54 | files = ( 55 | 739180FB1F6677BE001EACFF /* LPAlbum.framework in CopyFiles */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXCopyFilesBuildPhase section */ 60 | 61 | /* Begin PBXFileReference section */ 62 | 7319E28B1F7A645F0048B3C7 /* PhotoPreviewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPreviewController.swift; sourceTree = ""; }; 63 | 731A47FE1F623AF000AC5513 /* LPAlbum.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LPAlbum.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 731A48011F623AF000AC5513 /* LPAlbum.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LPAlbum.h; sourceTree = ""; }; 65 | 731A48021F623AF000AC5513 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 66 | 731A48071F623AF000AC5513 /* LPAlbumTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LPAlbumTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | 731A480C1F623AF000AC5513 /* LPAlbumTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LPAlbumTests.swift; sourceTree = ""; }; 68 | 731A480E1F623AF000AC5513 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 69 | 731A481C1F623B3E00AC5513 /* LPAlbumDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LPAlbumDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | 731A481E1F623B3E00AC5513 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 71 | 731A48201F623B3E00AC5513 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 72 | 731A48231F623B3E00AC5513 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 73 | 731A48251F623B3E00AC5513 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 74 | 731A48281F623B3E00AC5513 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 75 | 731A482A1F623B3E00AC5513 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 76 | 7337B6731F6850C800F1FE84 /* PhotoCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCell.swift; sourceTree = ""; }; 77 | 738C26031F67934D00040E4E /* PhotosBrowerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotosBrowerController.swift; sourceTree = ""; }; 78 | 738C26051F67C94500040E4E /* TakeCameraCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TakeCameraCell.swift; sourceTree = ""; }; 79 | 738C260A1F67E43200040E4E /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 80 | 739181071F669FC9001EACFF /* LPAlbum.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LPAlbum.swift; sourceTree = ""; }; 81 | 739181081F669FC9001EACFF /* LPNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LPNavigationController.swift; sourceTree = ""; }; 82 | 7391810C1F669FCE001EACFF /* AlbumError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumError.swift; sourceTree = ""; }; 83 | 7391810D1F669FCE001EACFF /* AlbumManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumManager.swift; sourceTree = ""; }; 84 | 7391810E1F669FCE001EACFF /* AuthorizationTool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationTool.swift; sourceTree = ""; }; 85 | 7391810F1F669FCE001EACFF /* LPAlbum.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = LPAlbum.bundle; sourceTree = ""; }; 86 | 739181111F669FCE001EACFF /* Models.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; 87 | 739181191F669FD2001EACFF /* AlbumCollectionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumCollectionCell.swift; sourceTree = ""; }; 88 | 7391811B1F66A33C001EACFF /* DropMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropMenuView.swift; sourceTree = ""; }; 89 | 7391811C1F66A33C001EACFF /* TitleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleView.swift; sourceTree = ""; }; 90 | 73A85A871F68EDF000C3096E /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 91 | 73BF7BEF1F6A24E400A0BEC1 /* PhotoPreviewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoPreviewCell.swift; sourceTree = ""; }; 92 | /* End PBXFileReference section */ 93 | 94 | /* Begin PBXFrameworksBuildPhase section */ 95 | 731A47FA1F623AF000AC5513 /* Frameworks */ = { 96 | isa = PBXFrameworksBuildPhase; 97 | buildActionMask = 2147483647; 98 | files = ( 99 | ); 100 | runOnlyForDeploymentPostprocessing = 0; 101 | }; 102 | 731A48041F623AF000AC5513 /* Frameworks */ = { 103 | isa = PBXFrameworksBuildPhase; 104 | buildActionMask = 2147483647; 105 | files = ( 106 | 731A48081F623AF000AC5513 /* LPAlbum.framework in Frameworks */, 107 | ); 108 | runOnlyForDeploymentPostprocessing = 0; 109 | }; 110 | 731A48191F623B3E00AC5513 /* Frameworks */ = { 111 | isa = PBXFrameworksBuildPhase; 112 | buildActionMask = 2147483647; 113 | files = ( 114 | ); 115 | runOnlyForDeploymentPostprocessing = 0; 116 | }; 117 | /* End PBXFrameworksBuildPhase section */ 118 | 119 | /* Begin PBXGroup section */ 120 | 731A47F41F623AEF00AC5513 = { 121 | isa = PBXGroup; 122 | children = ( 123 | 731A48001F623AF000AC5513 /* LPAlbum */, 124 | 731A480B1F623AF000AC5513 /* LPAlbumTests */, 125 | 731A481D1F623B3E00AC5513 /* LPAlbumDemo */, 126 | 731A47FF1F623AF000AC5513 /* Products */, 127 | ); 128 | sourceTree = ""; 129 | }; 130 | 731A47FF1F623AF000AC5513 /* Products */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 731A47FE1F623AF000AC5513 /* LPAlbum.framework */, 134 | 731A48071F623AF000AC5513 /* LPAlbumTests.xctest */, 135 | 731A481C1F623B3E00AC5513 /* LPAlbumDemo.app */, 136 | ); 137 | name = Products; 138 | sourceTree = ""; 139 | }; 140 | 731A48001F623AF000AC5513 /* LPAlbum */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 739181181F669FD2001EACFF /* Views */, 144 | 7391810B1F669FCE001EACFF /* Others */, 145 | 739181061F669FC9001EACFF /* Controllers */, 146 | 731A48011F623AF000AC5513 /* LPAlbum.h */, 147 | 731A48021F623AF000AC5513 /* Info.plist */, 148 | ); 149 | path = LPAlbum; 150 | sourceTree = ""; 151 | }; 152 | 731A480B1F623AF000AC5513 /* LPAlbumTests */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | 731A480C1F623AF000AC5513 /* LPAlbumTests.swift */, 156 | 731A480E1F623AF000AC5513 /* Info.plist */, 157 | ); 158 | path = LPAlbumTests; 159 | sourceTree = ""; 160 | }; 161 | 731A481D1F623B3E00AC5513 /* LPAlbumDemo */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | 731A481E1F623B3E00AC5513 /* AppDelegate.swift */, 165 | 731A48201F623B3E00AC5513 /* ViewController.swift */, 166 | 731A48221F623B3E00AC5513 /* Main.storyboard */, 167 | 731A48251F623B3E00AC5513 /* Assets.xcassets */, 168 | 731A48271F623B3E00AC5513 /* LaunchScreen.storyboard */, 169 | 731A482A1F623B3E00AC5513 /* Info.plist */, 170 | 7337B6731F6850C800F1FE84 /* PhotoCell.swift */, 171 | ); 172 | path = LPAlbumDemo; 173 | sourceTree = ""; 174 | }; 175 | 739181061F669FC9001EACFF /* Controllers */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | 739181071F669FC9001EACFF /* LPAlbum.swift */, 179 | 739181081F669FC9001EACFF /* LPNavigationController.swift */, 180 | 738C26031F67934D00040E4E /* PhotosBrowerController.swift */, 181 | 7319E28B1F7A645F0048B3C7 /* PhotoPreviewController.swift */, 182 | ); 183 | path = Controllers; 184 | sourceTree = ""; 185 | }; 186 | 7391810B1F669FCE001EACFF /* Others */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | 7391810C1F669FCE001EACFF /* AlbumError.swift */, 190 | 7391810D1F669FCE001EACFF /* AlbumManager.swift */, 191 | 7391810E1F669FCE001EACFF /* AuthorizationTool.swift */, 192 | 7391810F1F669FCE001EACFF /* LPAlbum.bundle */, 193 | 739181111F669FCE001EACFF /* Models.swift */, 194 | 738C260A1F67E43200040E4E /* Extensions.swift */, 195 | 73A85A871F68EDF000C3096E /* Config.swift */, 196 | ); 197 | path = Others; 198 | sourceTree = ""; 199 | }; 200 | 739181181F669FD2001EACFF /* Views */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | 7391811B1F66A33C001EACFF /* DropMenuView.swift */, 204 | 7391811C1F66A33C001EACFF /* TitleView.swift */, 205 | 739181191F669FD2001EACFF /* AlbumCollectionCell.swift */, 206 | 738C26051F67C94500040E4E /* TakeCameraCell.swift */, 207 | 73BF7BEF1F6A24E400A0BEC1 /* PhotoPreviewCell.swift */, 208 | ); 209 | path = Views; 210 | sourceTree = ""; 211 | }; 212 | /* End PBXGroup section */ 213 | 214 | /* Begin PBXHeadersBuildPhase section */ 215 | 731A47FB1F623AF000AC5513 /* Headers */ = { 216 | isa = PBXHeadersBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | 731A480F1F623AF000AC5513 /* LPAlbum.h in Headers */, 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | }; 223 | /* End PBXHeadersBuildPhase section */ 224 | 225 | /* Begin PBXNativeTarget section */ 226 | 731A47FD1F623AF000AC5513 /* LPAlbum */ = { 227 | isa = PBXNativeTarget; 228 | buildConfigurationList = 731A48121F623AF000AC5513 /* Build configuration list for PBXNativeTarget "LPAlbum" */; 229 | buildPhases = ( 230 | 731A47F91F623AF000AC5513 /* Sources */, 231 | 731A47FA1F623AF000AC5513 /* Frameworks */, 232 | 731A47FB1F623AF000AC5513 /* Headers */, 233 | 731A47FC1F623AF000AC5513 /* Resources */, 234 | ); 235 | buildRules = ( 236 | ); 237 | dependencies = ( 238 | ); 239 | name = LPAlbum; 240 | productName = LPAlbum; 241 | productReference = 731A47FE1F623AF000AC5513 /* LPAlbum.framework */; 242 | productType = "com.apple.product-type.framework"; 243 | }; 244 | 731A48061F623AF000AC5513 /* LPAlbumTests */ = { 245 | isa = PBXNativeTarget; 246 | buildConfigurationList = 731A48151F623AF000AC5513 /* Build configuration list for PBXNativeTarget "LPAlbumTests" */; 247 | buildPhases = ( 248 | 731A48031F623AF000AC5513 /* Sources */, 249 | 731A48041F623AF000AC5513 /* Frameworks */, 250 | 731A48051F623AF000AC5513 /* Resources */, 251 | ); 252 | buildRules = ( 253 | ); 254 | dependencies = ( 255 | 731A480A1F623AF000AC5513 /* PBXTargetDependency */, 256 | ); 257 | name = LPAlbumTests; 258 | productName = LPAlbumTests; 259 | productReference = 731A48071F623AF000AC5513 /* LPAlbumTests.xctest */; 260 | productType = "com.apple.product-type.bundle.unit-test"; 261 | }; 262 | 731A481B1F623B3E00AC5513 /* LPAlbumDemo */ = { 263 | isa = PBXNativeTarget; 264 | buildConfigurationList = 731A48361F623B3E00AC5513 /* Build configuration list for PBXNativeTarget "LPAlbumDemo" */; 265 | buildPhases = ( 266 | 731A48181F623B3E00AC5513 /* Sources */, 267 | 731A48191F623B3E00AC5513 /* Frameworks */, 268 | 731A481A1F623B3E00AC5513 /* Resources */, 269 | 739180FA1F667792001EACFF /* CopyFiles */, 270 | ); 271 | buildRules = ( 272 | ); 273 | dependencies = ( 274 | ); 275 | name = LPAlbumDemo; 276 | productName = LPAlbumDemo; 277 | productReference = 731A481C1F623B3E00AC5513 /* LPAlbumDemo.app */; 278 | productType = "com.apple.product-type.application"; 279 | }; 280 | /* End PBXNativeTarget section */ 281 | 282 | /* Begin PBXProject section */ 283 | 731A47F51F623AF000AC5513 /* Project object */ = { 284 | isa = PBXProject; 285 | attributes = { 286 | LastSwiftUpdateCheck = 0830; 287 | LastUpgradeCheck = 0830; 288 | ORGANIZATIONNAME = Loopeer; 289 | TargetAttributes = { 290 | 731A47FD1F623AF000AC5513 = { 291 | CreatedOnToolsVersion = 8.3.3; 292 | DevelopmentTeam = VS86A5N9W7; 293 | LastSwiftMigration = 0920; 294 | ProvisioningStyle = Automatic; 295 | }; 296 | 731A48061F623AF000AC5513 = { 297 | CreatedOnToolsVersion = 8.3.3; 298 | DevelopmentTeam = VS86A5N9W7; 299 | ProvisioningStyle = Automatic; 300 | }; 301 | 731A481B1F623B3E00AC5513 = { 302 | CreatedOnToolsVersion = 8.3.3; 303 | DevelopmentTeam = VS86A5N9W7; 304 | LastSwiftMigration = 0920; 305 | ProvisioningStyle = Automatic; 306 | }; 307 | }; 308 | }; 309 | buildConfigurationList = 731A47F81F623AF000AC5513 /* Build configuration list for PBXProject "LPAlbum" */; 310 | compatibilityVersion = "Xcode 3.2"; 311 | developmentRegion = English; 312 | hasScannedForEncodings = 0; 313 | knownRegions = ( 314 | en, 315 | Base, 316 | ); 317 | mainGroup = 731A47F41F623AEF00AC5513; 318 | productRefGroup = 731A47FF1F623AF000AC5513 /* Products */; 319 | projectDirPath = ""; 320 | projectRoot = ""; 321 | targets = ( 322 | 731A47FD1F623AF000AC5513 /* LPAlbum */, 323 | 731A48061F623AF000AC5513 /* LPAlbumTests */, 324 | 731A481B1F623B3E00AC5513 /* LPAlbumDemo */, 325 | ); 326 | }; 327 | /* End PBXProject section */ 328 | 329 | /* Begin PBXResourcesBuildPhase section */ 330 | 731A47FC1F623AF000AC5513 /* Resources */ = { 331 | isa = PBXResourcesBuildPhase; 332 | buildActionMask = 2147483647; 333 | files = ( 334 | 739181151F669FCE001EACFF /* LPAlbum.bundle in Resources */, 335 | ); 336 | runOnlyForDeploymentPostprocessing = 0; 337 | }; 338 | 731A48051F623AF000AC5513 /* Resources */ = { 339 | isa = PBXResourcesBuildPhase; 340 | buildActionMask = 2147483647; 341 | files = ( 342 | ); 343 | runOnlyForDeploymentPostprocessing = 0; 344 | }; 345 | 731A481A1F623B3E00AC5513 /* Resources */ = { 346 | isa = PBXResourcesBuildPhase; 347 | buildActionMask = 2147483647; 348 | files = ( 349 | 731A48291F623B3E00AC5513 /* LaunchScreen.storyboard in Resources */, 350 | 731A48261F623B3E00AC5513 /* Assets.xcassets in Resources */, 351 | 731A48241F623B3E00AC5513 /* Main.storyboard in Resources */, 352 | ); 353 | runOnlyForDeploymentPostprocessing = 0; 354 | }; 355 | /* End PBXResourcesBuildPhase section */ 356 | 357 | /* Begin PBXSourcesBuildPhase section */ 358 | 731A47F91F623AF000AC5513 /* Sources */ = { 359 | isa = PBXSourcesBuildPhase; 360 | buildActionMask = 2147483647; 361 | files = ( 362 | 739181091F669FC9001EACFF /* LPAlbum.swift in Sources */, 363 | 7391811D1F66A33C001EACFF /* DropMenuView.swift in Sources */, 364 | 738C26041F67934D00040E4E /* PhotosBrowerController.swift in Sources */, 365 | 7391811A1F669FD2001EACFF /* AlbumCollectionCell.swift in Sources */, 366 | 738C26061F67C94500040E4E /* TakeCameraCell.swift in Sources */, 367 | 73BF7BF01F6A24E400A0BEC1 /* PhotoPreviewCell.swift in Sources */, 368 | 739181131F669FCE001EACFF /* AlbumManager.swift in Sources */, 369 | 73A85A881F68EDF000C3096E /* Config.swift in Sources */, 370 | 739181141F669FCE001EACFF /* AuthorizationTool.swift in Sources */, 371 | 738C260B1F67E43200040E4E /* Extensions.swift in Sources */, 372 | 7391810A1F669FC9001EACFF /* LPNavigationController.swift in Sources */, 373 | 7319E28C1F7A645F0048B3C7 /* PhotoPreviewController.swift in Sources */, 374 | 739181121F669FCE001EACFF /* AlbumError.swift in Sources */, 375 | 739181171F669FCE001EACFF /* Models.swift in Sources */, 376 | 7391811F1F66B3A4001EACFF /* TitleView.swift in Sources */, 377 | ); 378 | runOnlyForDeploymentPostprocessing = 0; 379 | }; 380 | 731A48031F623AF000AC5513 /* Sources */ = { 381 | isa = PBXSourcesBuildPhase; 382 | buildActionMask = 2147483647; 383 | files = ( 384 | 731A480D1F623AF000AC5513 /* LPAlbumTests.swift in Sources */, 385 | ); 386 | runOnlyForDeploymentPostprocessing = 0; 387 | }; 388 | 731A48181F623B3E00AC5513 /* Sources */ = { 389 | isa = PBXSourcesBuildPhase; 390 | buildActionMask = 2147483647; 391 | files = ( 392 | 7337B6741F6850C800F1FE84 /* PhotoCell.swift in Sources */, 393 | 731A48211F623B3E00AC5513 /* ViewController.swift in Sources */, 394 | 731A481F1F623B3E00AC5513 /* AppDelegate.swift in Sources */, 395 | ); 396 | runOnlyForDeploymentPostprocessing = 0; 397 | }; 398 | /* End PBXSourcesBuildPhase section */ 399 | 400 | /* Begin PBXTargetDependency section */ 401 | 731A480A1F623AF000AC5513 /* PBXTargetDependency */ = { 402 | isa = PBXTargetDependency; 403 | target = 731A47FD1F623AF000AC5513 /* LPAlbum */; 404 | targetProxy = 731A48091F623AF000AC5513 /* PBXContainerItemProxy */; 405 | }; 406 | /* End PBXTargetDependency section */ 407 | 408 | /* Begin PBXVariantGroup section */ 409 | 731A48221F623B3E00AC5513 /* Main.storyboard */ = { 410 | isa = PBXVariantGroup; 411 | children = ( 412 | 731A48231F623B3E00AC5513 /* Base */, 413 | ); 414 | name = Main.storyboard; 415 | sourceTree = ""; 416 | }; 417 | 731A48271F623B3E00AC5513 /* LaunchScreen.storyboard */ = { 418 | isa = PBXVariantGroup; 419 | children = ( 420 | 731A48281F623B3E00AC5513 /* Base */, 421 | ); 422 | name = LaunchScreen.storyboard; 423 | sourceTree = ""; 424 | }; 425 | /* End PBXVariantGroup section */ 426 | 427 | /* Begin XCBuildConfiguration section */ 428 | 731A48101F623AF000AC5513 /* Debug */ = { 429 | isa = XCBuildConfiguration; 430 | buildSettings = { 431 | ALWAYS_SEARCH_USER_PATHS = NO; 432 | CLANG_ANALYZER_NONNULL = YES; 433 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 434 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 435 | CLANG_CXX_LIBRARY = "libc++"; 436 | CLANG_ENABLE_MODULES = YES; 437 | CLANG_ENABLE_OBJC_ARC = YES; 438 | CLANG_WARN_BOOL_CONVERSION = YES; 439 | CLANG_WARN_CONSTANT_CONVERSION = YES; 440 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 441 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 442 | CLANG_WARN_EMPTY_BODY = YES; 443 | CLANG_WARN_ENUM_CONVERSION = YES; 444 | CLANG_WARN_INFINITE_RECURSION = YES; 445 | CLANG_WARN_INT_CONVERSION = YES; 446 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 447 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 448 | CLANG_WARN_UNREACHABLE_CODE = YES; 449 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 450 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 451 | COPY_PHASE_STRIP = NO; 452 | CURRENT_PROJECT_VERSION = 1; 453 | DEBUG_INFORMATION_FORMAT = dwarf; 454 | ENABLE_STRICT_OBJC_MSGSEND = YES; 455 | ENABLE_TESTABILITY = YES; 456 | GCC_C_LANGUAGE_STANDARD = gnu99; 457 | GCC_DYNAMIC_NO_PIC = NO; 458 | GCC_NO_COMMON_BLOCKS = YES; 459 | GCC_OPTIMIZATION_LEVEL = 0; 460 | GCC_PREPROCESSOR_DEFINITIONS = ( 461 | "DEBUG=1", 462 | "$(inherited)", 463 | ); 464 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 465 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 466 | GCC_WARN_UNDECLARED_SELECTOR = YES; 467 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 468 | GCC_WARN_UNUSED_FUNCTION = YES; 469 | GCC_WARN_UNUSED_VARIABLE = YES; 470 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 471 | MTL_ENABLE_DEBUG_INFO = YES; 472 | ONLY_ACTIVE_ARCH = YES; 473 | SDKROOT = iphoneos; 474 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 475 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 476 | TARGETED_DEVICE_FAMILY = "1,2"; 477 | VERSIONING_SYSTEM = "apple-generic"; 478 | VERSION_INFO_PREFIX = ""; 479 | }; 480 | name = Debug; 481 | }; 482 | 731A48111F623AF000AC5513 /* Release */ = { 483 | isa = XCBuildConfiguration; 484 | buildSettings = { 485 | ALWAYS_SEARCH_USER_PATHS = NO; 486 | CLANG_ANALYZER_NONNULL = YES; 487 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 488 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 489 | CLANG_CXX_LIBRARY = "libc++"; 490 | CLANG_ENABLE_MODULES = YES; 491 | CLANG_ENABLE_OBJC_ARC = YES; 492 | CLANG_WARN_BOOL_CONVERSION = YES; 493 | CLANG_WARN_CONSTANT_CONVERSION = YES; 494 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 495 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 496 | CLANG_WARN_EMPTY_BODY = YES; 497 | CLANG_WARN_ENUM_CONVERSION = YES; 498 | CLANG_WARN_INFINITE_RECURSION = YES; 499 | CLANG_WARN_INT_CONVERSION = YES; 500 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 501 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 502 | CLANG_WARN_UNREACHABLE_CODE = YES; 503 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 504 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 505 | COPY_PHASE_STRIP = NO; 506 | CURRENT_PROJECT_VERSION = 1; 507 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 508 | ENABLE_NS_ASSERTIONS = NO; 509 | ENABLE_STRICT_OBJC_MSGSEND = YES; 510 | GCC_C_LANGUAGE_STANDARD = gnu99; 511 | GCC_NO_COMMON_BLOCKS = YES; 512 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 513 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 514 | GCC_WARN_UNDECLARED_SELECTOR = YES; 515 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 516 | GCC_WARN_UNUSED_FUNCTION = YES; 517 | GCC_WARN_UNUSED_VARIABLE = YES; 518 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 519 | MTL_ENABLE_DEBUG_INFO = NO; 520 | SDKROOT = iphoneos; 521 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 522 | TARGETED_DEVICE_FAMILY = "1,2"; 523 | VALIDATE_PRODUCT = YES; 524 | VERSIONING_SYSTEM = "apple-generic"; 525 | VERSION_INFO_PREFIX = ""; 526 | }; 527 | name = Release; 528 | }; 529 | 731A48131F623AF000AC5513 /* Debug */ = { 530 | isa = XCBuildConfiguration; 531 | buildSettings = { 532 | CLANG_ENABLE_MODULES = YES; 533 | CODE_SIGN_IDENTITY = ""; 534 | DEFINES_MODULE = YES; 535 | DEVELOPMENT_TEAM = VS86A5N9W7; 536 | DYLIB_COMPATIBILITY_VERSION = 1; 537 | DYLIB_CURRENT_VERSION = 1; 538 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 539 | INFOPLIST_FILE = LPAlbum/Info.plist; 540 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 541 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 542 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 543 | PRODUCT_BUNDLE_IDENTIFIER = Loopeer.LPAlbum; 544 | PRODUCT_NAME = "$(TARGET_NAME)"; 545 | SKIP_INSTALL = YES; 546 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 547 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 548 | SWIFT_VERSION = 4.0; 549 | }; 550 | name = Debug; 551 | }; 552 | 731A48141F623AF000AC5513 /* Release */ = { 553 | isa = XCBuildConfiguration; 554 | buildSettings = { 555 | CLANG_ENABLE_MODULES = YES; 556 | CODE_SIGN_IDENTITY = ""; 557 | DEFINES_MODULE = YES; 558 | DEVELOPMENT_TEAM = VS86A5N9W7; 559 | DYLIB_COMPATIBILITY_VERSION = 1; 560 | DYLIB_CURRENT_VERSION = 1; 561 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 562 | INFOPLIST_FILE = LPAlbum/Info.plist; 563 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 564 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 565 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 566 | PRODUCT_BUNDLE_IDENTIFIER = Loopeer.LPAlbum; 567 | PRODUCT_NAME = "$(TARGET_NAME)"; 568 | SKIP_INSTALL = YES; 569 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 570 | SWIFT_VERSION = 4.0; 571 | }; 572 | name = Release; 573 | }; 574 | 731A48161F623AF000AC5513 /* Debug */ = { 575 | isa = XCBuildConfiguration; 576 | buildSettings = { 577 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 578 | DEVELOPMENT_TEAM = VS86A5N9W7; 579 | INFOPLIST_FILE = LPAlbumTests/Info.plist; 580 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 581 | PRODUCT_BUNDLE_IDENTIFIER = Loopeer.LPAlbumTests; 582 | PRODUCT_NAME = "$(TARGET_NAME)"; 583 | SWIFT_VERSION = 4.0; 584 | }; 585 | name = Debug; 586 | }; 587 | 731A48171F623AF000AC5513 /* Release */ = { 588 | isa = XCBuildConfiguration; 589 | buildSettings = { 590 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 591 | DEVELOPMENT_TEAM = VS86A5N9W7; 592 | INFOPLIST_FILE = LPAlbumTests/Info.plist; 593 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 594 | PRODUCT_BUNDLE_IDENTIFIER = Loopeer.LPAlbumTests; 595 | PRODUCT_NAME = "$(TARGET_NAME)"; 596 | SWIFT_VERSION = 4.0; 597 | }; 598 | name = Release; 599 | }; 600 | 731A48371F623B3E00AC5513 /* Debug */ = { 601 | isa = XCBuildConfiguration; 602 | buildSettings = { 603 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 604 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 605 | DEVELOPMENT_TEAM = VS86A5N9W7; 606 | INFOPLIST_FILE = LPAlbumDemo/Info.plist; 607 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 608 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 609 | PRODUCT_BUNDLE_IDENTIFIER = Loopeer.LPAlbumDemo; 610 | PRODUCT_NAME = "$(TARGET_NAME)"; 611 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 612 | SWIFT_VERSION = 4.0; 613 | }; 614 | name = Debug; 615 | }; 616 | 731A48381F623B3E00AC5513 /* Release */ = { 617 | isa = XCBuildConfiguration; 618 | buildSettings = { 619 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 620 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 621 | DEVELOPMENT_TEAM = VS86A5N9W7; 622 | INFOPLIST_FILE = LPAlbumDemo/Info.plist; 623 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 624 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 625 | PRODUCT_BUNDLE_IDENTIFIER = Loopeer.LPAlbumDemo; 626 | PRODUCT_NAME = "$(TARGET_NAME)"; 627 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 628 | SWIFT_VERSION = 4.0; 629 | }; 630 | name = Release; 631 | }; 632 | /* End XCBuildConfiguration section */ 633 | 634 | /* Begin XCConfigurationList section */ 635 | 731A47F81F623AF000AC5513 /* Build configuration list for PBXProject "LPAlbum" */ = { 636 | isa = XCConfigurationList; 637 | buildConfigurations = ( 638 | 731A48101F623AF000AC5513 /* Debug */, 639 | 731A48111F623AF000AC5513 /* Release */, 640 | ); 641 | defaultConfigurationIsVisible = 0; 642 | defaultConfigurationName = Release; 643 | }; 644 | 731A48121F623AF000AC5513 /* Build configuration list for PBXNativeTarget "LPAlbum" */ = { 645 | isa = XCConfigurationList; 646 | buildConfigurations = ( 647 | 731A48131F623AF000AC5513 /* Debug */, 648 | 731A48141F623AF000AC5513 /* Release */, 649 | ); 650 | defaultConfigurationIsVisible = 0; 651 | defaultConfigurationName = Release; 652 | }; 653 | 731A48151F623AF000AC5513 /* Build configuration list for PBXNativeTarget "LPAlbumTests" */ = { 654 | isa = XCConfigurationList; 655 | buildConfigurations = ( 656 | 731A48161F623AF000AC5513 /* Debug */, 657 | 731A48171F623AF000AC5513 /* Release */, 658 | ); 659 | defaultConfigurationIsVisible = 0; 660 | defaultConfigurationName = Release; 661 | }; 662 | 731A48361F623B3E00AC5513 /* Build configuration list for PBXNativeTarget "LPAlbumDemo" */ = { 663 | isa = XCConfigurationList; 664 | buildConfigurations = ( 665 | 731A48371F623B3E00AC5513 /* Debug */, 666 | 731A48381F623B3E00AC5513 /* Release */, 667 | ); 668 | defaultConfigurationIsVisible = 0; 669 | defaultConfigurationName = Release; 670 | }; 671 | /* End XCConfigurationList section */ 672 | }; 673 | rootObject = 731A47F51F623AF000AC5513 /* Project object */; 674 | } 675 | --------------------------------------------------------------------------------