├── .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 |
4 |
5 | 
6 | 
7 | 
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 |
24 |
25 | |
26 |
27 |
28 | |
29 |
30 |
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 |
--------------------------------------------------------------------------------