From 356e601d6635b7613b3ef525165a1daa53710d72 Mon Sep 17 00:00:00 2001 From: gaoyu Date: Thu, 14 Sep 2017 00:25:05 +0800 Subject: [PATCH] init project --- .gitignore | 105 +++ LPAlbum.xcodeproj/project.pbxproj | 665 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../Controllers/AlbumDetailController.swift | 19 + LPAlbum/Controllers/LPAlbum.swift | 255 +++++++ .../Controllers/LPNavigationController.swift | 37 + LPAlbum/Info.plist | 24 + LPAlbum/LPAlbum.h | 19 + LPAlbum/Others/AlbumError.swift | 25 + LPAlbum/Others/AlbumManager.swift | 80 +++ LPAlbum/Others/AuthorizationTool.swift | 43 ++ LPAlbum/Others/Config.swift | 31 + LPAlbum/Others/Extensions.swift | 155 ++++ .../Images/circle_normal@2x.png | Bin 0 -> 1540 bytes .../Images/circle_normal@3x.png | Bin 0 -> 3814 bytes .../Images/circle_selected@2x.png | Bin 0 -> 4042 bytes .../Images/circle_selected@3x.png | Bin 0 -> 4615 bytes .../LPAlbum.bundle/Images/image_camera@2x.png | Bin 0 -> 933 bytes .../LPAlbum.bundle/Images/image_camera@3x.png | Bin 0 -> 1326 bytes .../LPAlbum.bundle/Images/meun_down@2x.png | Bin 0 -> 281 bytes .../LPAlbum.bundle/Images/meun_down@3x.png | Bin 0 -> 425 bytes LPAlbum/Others/Models.swift | 64 ++ LPAlbum/Views/AlbumCollectionCell.swift | 61 ++ LPAlbum/Views/DropMenuView.swift | 151 ++++ LPAlbum/Views/TakeCameraCell.swift | 30 + LPAlbum/Views/TitleView.swift | 52 ++ LPAlbumDemo/AlbumViewController.swift | 18 + LPAlbumDemo/AppDelegate.swift | 22 + .../AppIcon.appiconset/Contents.json | 48 ++ .../Base.lproj/LaunchScreen.storyboard | 27 + LPAlbumDemo/Base.lproj/Main.storyboard | 50 ++ LPAlbumDemo/Info.plist | 44 ++ LPAlbumDemo/PhotoCell.swift | 28 + LPAlbumDemo/ViewController.swift | 87 +++ LPAlbumTests/Info.plist | 22 + LPAlbumTests/LPAlbumTests.swift | 36 + 36 files changed, 2205 insertions(+) create mode 100644 .gitignore create mode 100644 LPAlbum.xcodeproj/project.pbxproj create mode 100644 LPAlbum.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 LPAlbum/Controllers/AlbumDetailController.swift create mode 100644 LPAlbum/Controllers/LPAlbum.swift create mode 100644 LPAlbum/Controllers/LPNavigationController.swift create mode 100644 LPAlbum/Info.plist create mode 100644 LPAlbum/LPAlbum.h create mode 100644 LPAlbum/Others/AlbumError.swift create mode 100644 LPAlbum/Others/AlbumManager.swift create mode 100644 LPAlbum/Others/AuthorizationTool.swift create mode 100644 LPAlbum/Others/Config.swift create mode 100644 LPAlbum/Others/Extensions.swift create mode 100755 LPAlbum/Others/LPAlbum.bundle/Images/circle_normal@2x.png create mode 100755 LPAlbum/Others/LPAlbum.bundle/Images/circle_normal@3x.png create mode 100755 LPAlbum/Others/LPAlbum.bundle/Images/circle_selected@2x.png create mode 100755 LPAlbum/Others/LPAlbum.bundle/Images/circle_selected@3x.png create mode 100644 LPAlbum/Others/LPAlbum.bundle/Images/image_camera@2x.png create mode 100644 LPAlbum/Others/LPAlbum.bundle/Images/image_camera@3x.png create mode 100644 LPAlbum/Others/LPAlbum.bundle/Images/meun_down@2x.png create mode 100644 LPAlbum/Others/LPAlbum.bundle/Images/meun_down@3x.png create mode 100644 LPAlbum/Others/Models.swift create mode 100644 LPAlbum/Views/AlbumCollectionCell.swift create mode 100644 LPAlbum/Views/DropMenuView.swift create mode 100644 LPAlbum/Views/TakeCameraCell.swift create mode 100644 LPAlbum/Views/TitleView.swift create mode 100644 LPAlbumDemo/AlbumViewController.swift create mode 100644 LPAlbumDemo/AppDelegate.swift create mode 100644 LPAlbumDemo/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 LPAlbumDemo/Base.lproj/LaunchScreen.storyboard create mode 100644 LPAlbumDemo/Base.lproj/Main.storyboard create mode 100644 LPAlbumDemo/Info.plist create mode 100644 LPAlbumDemo/PhotoCell.swift create mode 100644 LPAlbumDemo/ViewController.swift create mode 100644 LPAlbumTests/Info.plist create mode 100644 LPAlbumTests/LPAlbumTests.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d453c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,105 @@ + +# Created by https://www.gitignore.io/api/macos,swift + +### macOS ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +.build/ + +# CocoaPods - Refactored to standalone file + +# Carthage - Refactored to standalone file + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output + +### Swift.Carthage Stack ### +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +### Swift.CocoaPods Stack ### +## CocoaPods GitIgnore Template + +# CocoaPods - Only use to conserve bandwidth / Save time on Pushing +# - Also handy if you have a lage number of dependant pods +# - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGONRE THE LOCK FILE +Pods/ + +# End of https://www.gitignore.io/api/macos,swift \ No newline at end of file diff --git a/LPAlbum.xcodeproj/project.pbxproj b/LPAlbum.xcodeproj/project.pbxproj new file mode 100644 index 0000000..08b1ad6 --- /dev/null +++ b/LPAlbum.xcodeproj/project.pbxproj @@ -0,0 +1,665 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 731A48081F623AF000AC5513 /* LPAlbum.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 731A47FE1F623AF000AC5513 /* LPAlbum.framework */; }; + 731A480D1F623AF000AC5513 /* LPAlbumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731A480C1F623AF000AC5513 /* LPAlbumTests.swift */; }; + 731A480F1F623AF000AC5513 /* LPAlbum.h in Headers */ = {isa = PBXBuildFile; fileRef = 731A48011F623AF000AC5513 /* LPAlbum.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 731A481F1F623B3E00AC5513 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731A481E1F623B3E00AC5513 /* AppDelegate.swift */; }; + 731A48211F623B3E00AC5513 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731A48201F623B3E00AC5513 /* ViewController.swift */; }; + 731A48241F623B3E00AC5513 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 731A48221F623B3E00AC5513 /* Main.storyboard */; }; + 731A48261F623B3E00AC5513 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 731A48251F623B3E00AC5513 /* Assets.xcassets */; }; + 731A48291F623B3E00AC5513 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 731A48271F623B3E00AC5513 /* LaunchScreen.storyboard */; }; + 7337B6741F6850C800F1FE84 /* PhotoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7337B6731F6850C800F1FE84 /* PhotoCell.swift */; }; + 737FCD111F6241CC00C44C3D /* AlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 737FCD101F6241CC00C44C3D /* AlbumViewController.swift */; }; + 738C26041F67934D00040E4E /* AlbumDetailController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738C26031F67934D00040E4E /* AlbumDetailController.swift */; }; + 738C26061F67C94500040E4E /* TakeCameraCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738C26051F67C94500040E4E /* TakeCameraCell.swift */; }; + 738C260B1F67E43200040E4E /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738C260A1F67E43200040E4E /* Extensions.swift */; }; + 739180FB1F6677BE001EACFF /* LPAlbum.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 731A47FE1F623AF000AC5513 /* LPAlbum.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 739181091F669FC9001EACFF /* LPAlbum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739181071F669FC9001EACFF /* LPAlbum.swift */; }; + 7391810A1F669FC9001EACFF /* LPNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739181081F669FC9001EACFF /* LPNavigationController.swift */; }; + 739181121F669FCE001EACFF /* AlbumError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7391810C1F669FCE001EACFF /* AlbumError.swift */; }; + 739181131F669FCE001EACFF /* AlbumManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7391810D1F669FCE001EACFF /* AlbumManager.swift */; }; + 739181141F669FCE001EACFF /* AuthorizationTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7391810E1F669FCE001EACFF /* AuthorizationTool.swift */; }; + 739181151F669FCE001EACFF /* LPAlbum.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 7391810F1F669FCE001EACFF /* LPAlbum.bundle */; }; + 739181171F669FCE001EACFF /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739181111F669FCE001EACFF /* Models.swift */; }; + 7391811A1F669FD2001EACFF /* AlbumCollectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739181191F669FD2001EACFF /* AlbumCollectionCell.swift */; }; + 7391811D1F66A33C001EACFF /* DropMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7391811B1F66A33C001EACFF /* DropMenuView.swift */; }; + 7391811F1F66B3A4001EACFF /* TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7391811C1F66A33C001EACFF /* TitleView.swift */; }; + 73A85A881F68EDF000C3096E /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A85A871F68EDF000C3096E /* Config.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 731A48091F623AF000AC5513 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 731A47F51F623AF000AC5513 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 731A47FD1F623AF000AC5513; + remoteInfo = LPAlbum; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 739180FA1F667792001EACFF /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 739180FB1F6677BE001EACFF /* LPAlbum.framework in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 731A47FE1F623AF000AC5513 /* LPAlbum.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LPAlbum.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 731A48011F623AF000AC5513 /* LPAlbum.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LPAlbum.h; sourceTree = ""; }; + 731A48021F623AF000AC5513 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 731A48071F623AF000AC5513 /* LPAlbumTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LPAlbumTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 731A480C1F623AF000AC5513 /* LPAlbumTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LPAlbumTests.swift; sourceTree = ""; }; + 731A480E1F623AF000AC5513 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 731A481C1F623B3E00AC5513 /* LPAlbumDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LPAlbumDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 731A481E1F623B3E00AC5513 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 731A48201F623B3E00AC5513 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 731A48231F623B3E00AC5513 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 731A48251F623B3E00AC5513 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 731A48281F623B3E00AC5513 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 731A482A1F623B3E00AC5513 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7337B6731F6850C800F1FE84 /* PhotoCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCell.swift; sourceTree = ""; }; + 737FCD101F6241CC00C44C3D /* AlbumViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumViewController.swift; sourceTree = ""; }; + 738C26031F67934D00040E4E /* AlbumDetailController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumDetailController.swift; sourceTree = ""; }; + 738C26051F67C94500040E4E /* TakeCameraCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TakeCameraCell.swift; sourceTree = ""; }; + 738C260A1F67E43200040E4E /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + 739181071F669FC9001EACFF /* LPAlbum.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LPAlbum.swift; sourceTree = ""; }; + 739181081F669FC9001EACFF /* LPNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LPNavigationController.swift; sourceTree = ""; }; + 7391810C1F669FCE001EACFF /* AlbumError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumError.swift; sourceTree = ""; }; + 7391810D1F669FCE001EACFF /* AlbumManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumManager.swift; sourceTree = ""; }; + 7391810E1F669FCE001EACFF /* AuthorizationTool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationTool.swift; sourceTree = ""; }; + 7391810F1F669FCE001EACFF /* LPAlbum.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = LPAlbum.bundle; sourceTree = ""; }; + 739181111F669FCE001EACFF /* Models.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; + 739181191F669FD2001EACFF /* AlbumCollectionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumCollectionCell.swift; sourceTree = ""; }; + 7391811B1F66A33C001EACFF /* DropMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropMenuView.swift; sourceTree = ""; }; + 7391811C1F66A33C001EACFF /* TitleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleView.swift; sourceTree = ""; }; + 73A85A871F68EDF000C3096E /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 731A47FA1F623AF000AC5513 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 731A48041F623AF000AC5513 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 731A48081F623AF000AC5513 /* LPAlbum.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 731A48191F623B3E00AC5513 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 731A47F41F623AEF00AC5513 = { + isa = PBXGroup; + children = ( + 731A48001F623AF000AC5513 /* LPAlbum */, + 731A480B1F623AF000AC5513 /* LPAlbumTests */, + 731A481D1F623B3E00AC5513 /* LPAlbumDemo */, + 731A47FF1F623AF000AC5513 /* Products */, + ); + sourceTree = ""; + }; + 731A47FF1F623AF000AC5513 /* Products */ = { + isa = PBXGroup; + children = ( + 731A47FE1F623AF000AC5513 /* LPAlbum.framework */, + 731A48071F623AF000AC5513 /* LPAlbumTests.xctest */, + 731A481C1F623B3E00AC5513 /* LPAlbumDemo.app */, + ); + name = Products; + sourceTree = ""; + }; + 731A48001F623AF000AC5513 /* LPAlbum */ = { + isa = PBXGroup; + children = ( + 739181181F669FD2001EACFF /* Views */, + 7391810B1F669FCE001EACFF /* Others */, + 739181061F669FC9001EACFF /* Controllers */, + 731A48011F623AF000AC5513 /* LPAlbum.h */, + 731A48021F623AF000AC5513 /* Info.plist */, + ); + path = LPAlbum; + sourceTree = ""; + }; + 731A480B1F623AF000AC5513 /* LPAlbumTests */ = { + isa = PBXGroup; + children = ( + 731A480C1F623AF000AC5513 /* LPAlbumTests.swift */, + 731A480E1F623AF000AC5513 /* Info.plist */, + ); + path = LPAlbumTests; + sourceTree = ""; + }; + 731A481D1F623B3E00AC5513 /* LPAlbumDemo */ = { + isa = PBXGroup; + children = ( + 731A481E1F623B3E00AC5513 /* AppDelegate.swift */, + 731A48201F623B3E00AC5513 /* ViewController.swift */, + 731A48221F623B3E00AC5513 /* Main.storyboard */, + 731A48251F623B3E00AC5513 /* Assets.xcassets */, + 731A48271F623B3E00AC5513 /* LaunchScreen.storyboard */, + 731A482A1F623B3E00AC5513 /* Info.plist */, + 737FCD101F6241CC00C44C3D /* AlbumViewController.swift */, + 7337B6731F6850C800F1FE84 /* PhotoCell.swift */, + ); + path = LPAlbumDemo; + sourceTree = ""; + }; + 739181061F669FC9001EACFF /* Controllers */ = { + isa = PBXGroup; + children = ( + 739181071F669FC9001EACFF /* LPAlbum.swift */, + 739181081F669FC9001EACFF /* LPNavigationController.swift */, + 738C26031F67934D00040E4E /* AlbumDetailController.swift */, + ); + path = Controllers; + sourceTree = ""; + }; + 7391810B1F669FCE001EACFF /* Others */ = { + isa = PBXGroup; + children = ( + 7391810C1F669FCE001EACFF /* AlbumError.swift */, + 7391810D1F669FCE001EACFF /* AlbumManager.swift */, + 7391810E1F669FCE001EACFF /* AuthorizationTool.swift */, + 7391810F1F669FCE001EACFF /* LPAlbum.bundle */, + 739181111F669FCE001EACFF /* Models.swift */, + 738C260A1F67E43200040E4E /* Extensions.swift */, + 73A85A871F68EDF000C3096E /* Config.swift */, + ); + path = Others; + sourceTree = ""; + }; + 739181181F669FD2001EACFF /* Views */ = { + isa = PBXGroup; + children = ( + 7391811B1F66A33C001EACFF /* DropMenuView.swift */, + 7391811C1F66A33C001EACFF /* TitleView.swift */, + 739181191F669FD2001EACFF /* AlbumCollectionCell.swift */, + 738C26051F67C94500040E4E /* TakeCameraCell.swift */, + ); + path = Views; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 731A47FB1F623AF000AC5513 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 731A480F1F623AF000AC5513 /* LPAlbum.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 731A47FD1F623AF000AC5513 /* LPAlbum */ = { + isa = PBXNativeTarget; + buildConfigurationList = 731A48121F623AF000AC5513 /* Build configuration list for PBXNativeTarget "LPAlbum" */; + buildPhases = ( + 731A47F91F623AF000AC5513 /* Sources */, + 731A47FA1F623AF000AC5513 /* Frameworks */, + 731A47FB1F623AF000AC5513 /* Headers */, + 731A47FC1F623AF000AC5513 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = LPAlbum; + productName = LPAlbum; + productReference = 731A47FE1F623AF000AC5513 /* LPAlbum.framework */; + productType = "com.apple.product-type.framework"; + }; + 731A48061F623AF000AC5513 /* LPAlbumTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 731A48151F623AF000AC5513 /* Build configuration list for PBXNativeTarget "LPAlbumTests" */; + buildPhases = ( + 731A48031F623AF000AC5513 /* Sources */, + 731A48041F623AF000AC5513 /* Frameworks */, + 731A48051F623AF000AC5513 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 731A480A1F623AF000AC5513 /* PBXTargetDependency */, + ); + name = LPAlbumTests; + productName = LPAlbumTests; + productReference = 731A48071F623AF000AC5513 /* LPAlbumTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 731A481B1F623B3E00AC5513 /* LPAlbumDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 731A48361F623B3E00AC5513 /* Build configuration list for PBXNativeTarget "LPAlbumDemo" */; + buildPhases = ( + 731A48181F623B3E00AC5513 /* Sources */, + 731A48191F623B3E00AC5513 /* Frameworks */, + 731A481A1F623B3E00AC5513 /* Resources */, + 739180FA1F667792001EACFF /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = LPAlbumDemo; + productName = LPAlbumDemo; + productReference = 731A481C1F623B3E00AC5513 /* LPAlbumDemo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 731A47F51F623AF000AC5513 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0830; + LastUpgradeCheck = 0830; + ORGANIZATIONNAME = Loopeer; + TargetAttributes = { + 731A47FD1F623AF000AC5513 = { + CreatedOnToolsVersion = 8.3.3; + DevelopmentTeam = VS86A5N9W7; + LastSwiftMigration = 0830; + ProvisioningStyle = Automatic; + }; + 731A48061F623AF000AC5513 = { + CreatedOnToolsVersion = 8.3.3; + DevelopmentTeam = VS86A5N9W7; + ProvisioningStyle = Automatic; + }; + 731A481B1F623B3E00AC5513 = { + CreatedOnToolsVersion = 8.3.3; + DevelopmentTeam = VS86A5N9W7; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 731A47F81F623AF000AC5513 /* Build configuration list for PBXProject "LPAlbum" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 731A47F41F623AEF00AC5513; + productRefGroup = 731A47FF1F623AF000AC5513 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 731A47FD1F623AF000AC5513 /* LPAlbum */, + 731A48061F623AF000AC5513 /* LPAlbumTests */, + 731A481B1F623B3E00AC5513 /* LPAlbumDemo */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 731A47FC1F623AF000AC5513 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 739181151F669FCE001EACFF /* LPAlbum.bundle in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 731A48051F623AF000AC5513 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 731A481A1F623B3E00AC5513 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 731A48291F623B3E00AC5513 /* LaunchScreen.storyboard in Resources */, + 731A48261F623B3E00AC5513 /* Assets.xcassets in Resources */, + 731A48241F623B3E00AC5513 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 731A47F91F623AF000AC5513 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 739181091F669FC9001EACFF /* LPAlbum.swift in Sources */, + 7391811D1F66A33C001EACFF /* DropMenuView.swift in Sources */, + 738C26041F67934D00040E4E /* AlbumDetailController.swift in Sources */, + 7391811A1F669FD2001EACFF /* AlbumCollectionCell.swift in Sources */, + 738C26061F67C94500040E4E /* TakeCameraCell.swift in Sources */, + 739181131F669FCE001EACFF /* AlbumManager.swift in Sources */, + 73A85A881F68EDF000C3096E /* Config.swift in Sources */, + 739181141F669FCE001EACFF /* AuthorizationTool.swift in Sources */, + 738C260B1F67E43200040E4E /* Extensions.swift in Sources */, + 7391810A1F669FC9001EACFF /* LPNavigationController.swift in Sources */, + 739181121F669FCE001EACFF /* AlbumError.swift in Sources */, + 739181171F669FCE001EACFF /* Models.swift in Sources */, + 7391811F1F66B3A4001EACFF /* TitleView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 731A48031F623AF000AC5513 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 731A480D1F623AF000AC5513 /* LPAlbumTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 731A48181F623B3E00AC5513 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7337B6741F6850C800F1FE84 /* PhotoCell.swift in Sources */, + 737FCD111F6241CC00C44C3D /* AlbumViewController.swift in Sources */, + 731A48211F623B3E00AC5513 /* ViewController.swift in Sources */, + 731A481F1F623B3E00AC5513 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 731A480A1F623AF000AC5513 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 731A47FD1F623AF000AC5513 /* LPAlbum */; + targetProxy = 731A48091F623AF000AC5513 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 731A48221F623B3E00AC5513 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 731A48231F623B3E00AC5513 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 731A48271F623B3E00AC5513 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 731A48281F623B3E00AC5513 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 731A48101F623AF000AC5513 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 731A48111F623AF000AC5513 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 731A48131F623AF000AC5513 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = VS86A5N9W7; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = LPAlbum/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = Loopeer.LPAlbum; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 731A48141F623AF000AC5513 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = VS86A5N9W7; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = LPAlbum/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = Loopeer.LPAlbum; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + 731A48161F623AF000AC5513 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + DEVELOPMENT_TEAM = VS86A5N9W7; + INFOPLIST_FILE = LPAlbumTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = Loopeer.LPAlbumTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 731A48171F623AF000AC5513 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + DEVELOPMENT_TEAM = VS86A5N9W7; + INFOPLIST_FILE = LPAlbumTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = Loopeer.LPAlbumTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + 731A48371F623B3E00AC5513 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = VS86A5N9W7; + INFOPLIST_FILE = LPAlbumDemo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = Loopeer.LPAlbumDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 731A48381F623B3E00AC5513 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = VS86A5N9W7; + INFOPLIST_FILE = LPAlbumDemo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = Loopeer.LPAlbumDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 731A47F81F623AF000AC5513 /* Build configuration list for PBXProject "LPAlbum" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 731A48101F623AF000AC5513 /* Debug */, + 731A48111F623AF000AC5513 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 731A48121F623AF000AC5513 /* Build configuration list for PBXNativeTarget "LPAlbum" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 731A48131F623AF000AC5513 /* Debug */, + 731A48141F623AF000AC5513 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 731A48151F623AF000AC5513 /* Build configuration list for PBXNativeTarget "LPAlbumTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 731A48161F623AF000AC5513 /* Debug */, + 731A48171F623AF000AC5513 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 731A48361F623B3E00AC5513 /* Build configuration list for PBXNativeTarget "LPAlbumDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 731A48371F623B3E00AC5513 /* Debug */, + 731A48381F623B3E00AC5513 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 731A47F51F623AF000AC5513 /* Project object */; +} diff --git a/LPAlbum.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/LPAlbum.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..75a9ab5 --- /dev/null +++ b/LPAlbum.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/LPAlbum/Controllers/AlbumDetailController.swift b/LPAlbum/Controllers/AlbumDetailController.swift new file mode 100644 index 0000000..ccfaa60 --- /dev/null +++ b/LPAlbum/Controllers/AlbumDetailController.swift @@ -0,0 +1,19 @@ +// +// AlbumDetailController.swift +// LPAlbum +// +// Created by 郜宇 on 2017/9/12. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import UIKit + + +// TODO: 相册详情视图 +class AlbumDetailController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + } +} diff --git a/LPAlbum/Controllers/LPAlbum.swift b/LPAlbum/Controllers/LPAlbum.swift new file mode 100644 index 0000000..e51d75f --- /dev/null +++ b/LPAlbum/Controllers/LPAlbum.swift @@ -0,0 +1,255 @@ +// +// LPAlbum.swift +// LPAlbum +// +// Created by 郜宇 on 2017/9/8. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import UIKit +import Photos + +public typealias SetConfigBlock = ((inout LPAlbum.Config) -> Void) +public typealias CompleteSelectImagesBlock = (([UIImage]) -> Void) +public typealias ErrorBlock = ((UIViewController ,AlbumError) -> Void) +public typealias TargetSizeBlock = ((CGSize) -> CGSize) + +public class LPAlbum: UIViewController { + + fileprivate let config: Config + fileprivate var completeSelectImagesBlock: CompleteSelectImagesBlock? + fileprivate var errorBlock: ErrorBlock? + fileprivate var targetSizeBlock: TargetSizeBlock? + + fileprivate var cameraStatus: AVAuthorizationStatus! + fileprivate var albumStatus: PHAuthorizationStatus! + + fileprivate var albumManager: AlbumManager! + fileprivate var collectionView: UICollectionView! + fileprivate var titleView: TitleView! + fileprivate var menuView: DropMenuView! + + fileprivate var currentAlbumIndex: Int = 0 + fileprivate var albumModels = [AlbumModel]() + + + @discardableResult + public class func show(at: UIViewController, set: SetConfigBlock? = nil) -> LPAlbum { + var c = Config(); set?(&c); + let vc = LPAlbum(c) + // 异步延后调用, 使`errorBlock`不为空 + DispatchQueue.main.async { + let nav = LPNavigationController(rootViewController: vc) + AuthorizationTool.albumRRequestAuthorization { + $0 == .authorized ? at.present(nav, animated: true, completion: nil) : vc.errorBlock?(at, AlbumError.noAlbumPermission) + } + } + return vc + } + + @discardableResult + public func targeSize(_ map: @escaping TargetSizeBlock) -> Self{ + targetSizeBlock = map + return self + } + + @discardableResult + public func complete(_ complete: @escaping CompleteSelectImagesBlock) -> Self { + completeSelectImagesBlock = complete + return self + } + + @discardableResult + public func error(_ error: @escaping ErrorBlock) -> Self { + errorBlock = error + return self + } + + init(_ config: Config) { + self.config = config + super.init(nibName: nil, bundle: nil) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func loadView() { + super.loadView() + } + + public override func viewDidLoad() { + super.viewDidLoad() + + albumModels = AlbumManager.getAlbums() + setupUI() + titleView.clickAction = { [weak self] in + $0.isSelected = !$0.isSelected + $0.isSelected ? + self?.menuView.show(begin: { + self?.titleView.isEnabled = false + }, complete: { + self?.titleView.isEnabled = true + }) : + self?.menuView.dismiss(begin: { + self?.titleView.isEnabled = false + }, complete: { + self?.titleView.isEnabled = true + }) + } + menuView.maskViewClick = {[weak self] _ in + self?.titleView.isSelected = false + } + menuView.menuViewDidSelect = {[weak self] in + self?.titleView.isSelected = false + self?.currentAlbumIndex = $0 + self?.titleView.title = self?.albumModels[$0].name + self?.collectionView.reloadData() + } + } + + deinit { + print("\(self) deinit") + } +} + + +extension LPAlbum { + + func setupUI() { + navigationItem.leftBarButtonItem = UIBarButtonItem(title: "取消", style: .plain, target: self, action: #selector(cancel)) + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "完成", style: .plain, target: self, action: #selector(confirm)) + + titleView = TitleView(frame: .zero) + navigationItem.titleView = titleView + titleView.title = albumModels[currentAlbumIndex].name + + let layout = UICollectionViewFlowLayout() + let photoW = (view.bounds.width - config.photoPadding * CGFloat(config.columnCount - 1)) / CGFloat(config.columnCount) + layout.itemSize = CGSize(width: photoW, height: photoW) + layout.minimumLineSpacing = config.photoPadding + layout.minimumInteritemSpacing = config.photoPadding + collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height - 64), + collectionViewLayout: layout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.backgroundColor = .white + view.addSubview(collectionView) + + collectionView.register(AlbumCollectionCell.self, forCellWithReuseIdentifier: AlbumCollectionCell.description()) + collectionView.register(TakeCameraCell.self, forCellWithReuseIdentifier: TakeCameraCell.description()) + + menuView = DropMenuView(frame: view.bounds, albums: albumModels) + view.addSubview(menuView) + } + + func cancel() { dismiss(animated: true, completion: nil) } + func confirm() { + let assets = albumModels.allSelectedAsset + var result = [UIImage]() + for asset in assets { + let size = CGSize(width: asset.pixelWidth, height: asset.pixelHeight) //PHImageManagerMaximumSize + let targetSize = targetSizeBlock?(size) ?? size + let option = PHImageRequestOptions() + option.isSynchronous = true + option.resizeMode = .exact + AlbumManager.getPhoto(asset: asset, targetSize: targetSize, option: option, resultHandler: {[weak self] (image, _) in + if image != nil { result.append(image!) } + if result.count == assets.count { + self?.completeSelectImagesBlock?(result) + self?.cancel() } + }) + } + } +} + + +extension LPAlbum: UICollectionViewDelegate, UICollectionViewDataSource { + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + + if config.hasCamera && indexPath.row == 0 { + return collectionView.dequeueReusableCell(withReuseIdentifier: TakeCameraCell.description(), for: indexPath) as! TakeCameraCell + }else{ + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AlbumCollectionCell.description(), for: indexPath) as! AlbumCollectionCell + + let model = albumModels[currentAlbumIndex].assetModels[config.hasCamera ? indexPath.row - 1 : indexPath.row] + cell.set(model) + cell.iconClickAction = {[weak self] in + guard let `self` = self else { return } + guard self.checkoutMaxCount(willselect: !$0) else { return } + var newModel = model + newModel.isSelect = !$0 + self.albumModels = self.albumModels.change(assetModel: newModel) + self.collectionView.reloadItems(at: [indexPath]) + } + return cell + } + } + + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + let photoCount = albumModels[currentAlbumIndex].assetModels.count + return config.hasCamera ? photoCount + 1 : photoCount + } + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if config.hasCamera && indexPath.row == 0 { + guard checkoutMaxCount(willselect: true) else { return } + AuthorizationTool.cameraRequestAuthorization{ + $0 == .authorized ? self.takePhoto() : self.errorBlock?(self, AlbumError.noCameraPermission) + } + } + } + + func takePhoto() { + let sourceType = UIImagePickerControllerSourceType.camera + guard UIImagePickerController.isSourceTypeAvailable(sourceType) else { fatalError("摄像头不可用") } + let picker = UIImagePickerController() + picker.sourceType = sourceType + picker.allowsEditing = true + picker.delegate = self + present(picker, animated: true, completion: nil) + } + + func checkoutMaxCount(willselect: Bool) -> Bool { + if self.config.maxSelectCount == self.albumModels[0].selectCount && willselect { + self.errorBlock?(self,AlbumError.moreThanLargestChoiceCount) + return false + } + return true + } +} + +extension LPAlbum: UIImagePickerControllerDelegate, UINavigationControllerDelegate { + public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + picker.dismiss(animated: true, completion: nil) + } + public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { + + guard let image = (info[UIImagePickerControllerEditedImage] as? UIImage) ?? ( info[UIImagePickerControllerOriginalImage] as? UIImage) else { + picker.dismiss(animated: true, completion: nil); return; + } + + PHPhotoLibrary.shared().performChanges({ + PHAssetChangeRequest.creationRequestForAsset(from: image) + }) { [weak self] (success, error) in + guard let `self` = self else { return } + if success == false || error != nil { self.errorBlock?(self, AlbumError.savePhotoError) } + DispatchQueue.main.async { + let targetSize = self.targetSizeBlock?(image.size) ?? image.size + self.completeSelectImagesBlock?([image.scaleImage(to: targetSize, contentMode: .scaleAspectFill) ?? image]) + picker.dismiss(animated: true, completion: nil) + self.dismiss(animated: true, completion: nil) + } + } + } +} + + + + + + + + + + diff --git a/LPAlbum/Controllers/LPNavigationController.swift b/LPAlbum/Controllers/LPNavigationController.swift new file mode 100644 index 0000000..87b3c27 --- /dev/null +++ b/LPAlbum/Controllers/LPNavigationController.swift @@ -0,0 +1,37 @@ +// +// LPNavigationController.swift +// LPAlbum +// +// Created by 郜宇 on 2017/9/8. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import UIKit + +class LPNavigationController: UINavigationController { + + override var preferredStatusBarStyle: UIStatusBarStyle { + return LPAlbum.Config.statusBarStyle + } + + override func viewDidLoad() { + super.viewDidLoad() + navigationBar.isTranslucent = false + modalPresentationCapturesStatusBarAppearance = true + } + + override init(rootViewController: UIViewController) { + super.init(rootViewController: rootViewController) + self.navigationBar.barTintColor = LPAlbum.Config.barTintColor + self.navigationBar.tintColor = LPAlbum.Config.tintColor + } + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/LPAlbum/Info.plist b/LPAlbum/Info.plist new file mode 100644 index 0000000..fbe1e6b --- /dev/null +++ b/LPAlbum/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/LPAlbum/LPAlbum.h b/LPAlbum/LPAlbum.h new file mode 100644 index 0000000..420d69b --- /dev/null +++ b/LPAlbum/LPAlbum.h @@ -0,0 +1,19 @@ +// +// LPAlbum.h +// LPAlbum +// +// Created by 郜宇 on 2017/9/8. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +#import + +//! Project version number for LPAlbum. +FOUNDATION_EXPORT double LPAlbumVersionNumber; + +//! Project version string for LPAlbum. +FOUNDATION_EXPORT const unsigned char LPAlbumVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/LPAlbum/Others/AlbumError.swift b/LPAlbum/Others/AlbumError.swift new file mode 100644 index 0000000..e4321f3 --- /dev/null +++ b/LPAlbum/Others/AlbumError.swift @@ -0,0 +1,25 @@ +// +// LPAlbumError.swift +// LPAlbum +// +// Created by 郜宇 on 2017/9/11. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import Foundation + +public enum AlbumError: Error { + case noAlbumPermission + case noCameraPermission + case moreThanLargestChoiceCount + case savePhotoError + + public var localizedDescription: String { + switch self { + case .noAlbumPermission: return "没有相册访问权限" + case .noCameraPermission: return "没有相机访问权限" + case .moreThanLargestChoiceCount: return "超出了可选择图片数量的上限" + case .savePhotoError: return "保存图片失败" + } + } +} diff --git a/LPAlbum/Others/AlbumManager.swift b/LPAlbum/Others/AlbumManager.swift new file mode 100644 index 0000000..5dcfbbf --- /dev/null +++ b/LPAlbum/Others/AlbumManager.swift @@ -0,0 +1,80 @@ +// +// AlbumManager.swift +// LPAlbum +// +// Created by 郜宇 on 2017/9/11. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import Foundation +import Photos + +class AlbumManager { + + static let imageManager = PHCachingImageManager() + + class func getAlbums() -> [AlbumModel] { + //获取智能相册 + let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options: nil) + //获取用户创建的相册 + let userAlbums = PHAssetCollection.fetchTopLevelUserCollections(with: nil) + // 遍历相册 + let smartModels = enumerateAlbum(album: smartAlbums as! PHFetchResult) + let userModels = enumerateAlbum(album: userAlbums) + return (smartModels + userModels).sorted{ $0.count > $1.count } + } + + // 根据Asset获取photo + class func getPhoto(asset: PHAsset, targetSize: CGSize, option: PHImageRequestOptions? = nil, resultHandler: @escaping ((UIImage?, [AnyHashable: Any]?) -> Void)) { + + let defalutOption = PHImageRequestOptions() + defalutOption.resizeMode = .fast + defalutOption.deliveryMode = .opportunistic + + let size = CGSize(width: targetSize.width * UIScreen.main.scale, + height: targetSize.height * UIScreen.main.scale) + AlbumManager.imageManager.requestImage(for: asset, + targetSize: size, + contentMode: .aspectFill, + options: option ?? defalutOption, + resultHandler: resultHandler) + } + + private class func enumerateAlbum(album: PHFetchResult) -> [AlbumModel] { + + var result = [AlbumModel]() + + let options = PHFetchOptions() + options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] + options.predicate = NSPredicate(format: "mediaType in %@", [PHAssetMediaType.image.rawValue]) + + album.enumerateObjects(options: .concurrent) { (collection, index, stop) in + let assets = PHAsset.fetchAssets(in: collection as! PHAssetCollection, options: options) + if assets.count != 0 && (collection.localizedTitle?.isAvailableAlbum ?? false){ + let model = AlbumModel(name: collection.localizedTitle!, + cover: assets[0], + count: assets.count, + assetModels: assets.toAssetModels) + result.append(model) + } + } + return result + } + +} + + + + + + + + + + + + + + + + diff --git a/LPAlbum/Others/AuthorizationTool.swift b/LPAlbum/Others/AuthorizationTool.swift new file mode 100644 index 0000000..ab62398 --- /dev/null +++ b/LPAlbum/Others/AuthorizationTool.swift @@ -0,0 +1,43 @@ +// +// AuthorizationTool.swift +// LPAlbum +// +// Created by 郜宇 on 2017/9/8. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import Foundation +import Photos + +class AuthorizationTool { + /// 相机权限 + static func cameraRequestAuthorization(queue: DispatchQueue = .main, complete: @escaping (_ status: AVAuthorizationStatus) -> Void) { + switch AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo) { + case .authorized: complete(.authorized) + case .denied: complete(.denied) + case .restricted: complete(.restricted) + case .notDetermined: + queue.suspend() + AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo, completionHandler: { (granted) in + DispatchQueue.main.async { complete( granted ? .authorized : .denied ) } + queue.resume() + }) + } + } + + /// 相册权限 + static func albumRRequestAuthorization(queue: DispatchQueue = .main, complete: @escaping (_ status: PHAuthorizationStatus) -> Void){ + switch PHPhotoLibrary.authorizationStatus() { + case .authorized: complete(.authorized) + case .denied: complete(.denied) + case .restricted: complete(.restricted) + case .notDetermined: + queue.suspend() + PHPhotoLibrary.requestAuthorization({ (status) in + DispatchQueue.main.async { complete(status) } + queue.resume() + }) + } + } +} + diff --git a/LPAlbum/Others/Config.swift b/LPAlbum/Others/Config.swift new file mode 100644 index 0000000..5ddd025 --- /dev/null +++ b/LPAlbum/Others/Config.swift @@ -0,0 +1,31 @@ +// +// Config.swift +// LPAlbum +// +// Created by 郜宇 on 2017/9/13. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import Foundation + +public extension LPAlbum { + public struct Config { + + public var maxSelectCount: Int = 6 + public var columnCount: Int = 4 + public var photoPadding: CGFloat = 2.0 + public var hasCamera: Bool = true + public var isSingleSelect: Bool = false + + public static var barTitleColor: UIColor = UIColor.white + public static var barTintColor: UIColor = UIColor.darkGray + public static var tintColor: UIColor = UIColor.white + public static var statusBarStyle: UIStatusBarStyle = .lightContent + + public static var arrowImage: UIImage = Bundle.imageFromBundle("meun_down")! + public static var normalBox: UIImage = Bundle.imageFromBundle("circle_normal")! + public static var selectedBox: UIImage = Bundle.imageFromBundle("circle_selected")! + } +} + + diff --git a/LPAlbum/Others/Extensions.swift b/LPAlbum/Others/Extensions.swift new file mode 100644 index 0000000..477184c --- /dev/null +++ b/LPAlbum/Others/Extensions.swift @@ -0,0 +1,155 @@ +// +// Extensions.swift +// LPAlbum +// +// Created by 郜宇 on 2017/9/12. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import Foundation + +extension CGFloat { + static var screenWidth = UIScreen.main.bounds.width + static var screenHeight = UIScreen.main.bounds.height +} + +extension Int { + var cgFloat: CGFloat { return CGFloat(self) } + var half: CGFloat { return self.cgFloat / 2 } + var adapt: CGFloat { return self.cgFloat * CGFloat.screenWidth / 375.0 } +} + +extension Bundle { + // 资源的bundle + static var albumBundle: Bundle { + let bundle = Bundle(for: LPAlbum.self) + return Bundle(path: bundle.path(forResource: "LPAlbum", ofType: "bundle")!)! + } + + static func imageFromBundle(_ imageName: String) -> UIImage? { + var imageName = imageName + if UIScreen.main.scale == 2 { + imageName = imageName + "@2x" + }else if UIScreen.main.scale == 3 { + imageName = imageName + "@3x" + } + guard let bundle = Bundle(path: albumBundle.bundlePath + "/Images"), + let path = bundle.path(forResource: imageName, ofType: "png") else { return nil } + return UIImage(contentsOfFile: path) + } +} + + + +extension UIImage { + + func scaleImage(to size: CGSize, + contentMode: UIViewContentMode, + corner: CGFloat = 0, + backGroundColor: UIColor = UIColor.clear) -> UIImage? + { + UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) + + var newSize = self.size + var newImageFrame = CGRect(x: 0, y: 0, width: size.width, height: size.height) + + switch contentMode { + case .scaleAspectFit: + newSize = self.size.constrainedSize(toSize: size) + if newSize.width == size.width { + newImageFrame = CGRect(x: 0, + y: (size.height - newSize.height)/2, + width: newSize.width, + height: newSize.height) + } + if newSize.height == size.height { + newImageFrame = CGRect(x: 0, + y: (size.width - newSize.width)/2, + width: newSize.width, + height: newSize.height) + } + case .scaleAspectFill: + newSize = self.size.fillingSize(toSize: size) + if newSize.width > size.width { + newImageFrame = CGRect(x: -(newSize.width - size.width)/2, + y: 0, + width: newSize.width, + height: newSize.height) + } + if newSize.height > size.height { + newImageFrame = CGRect(x: 0, + y: -(newSize.height - size.height)/2, + width: newSize.width, + height: newSize.height) + } + default: break + } + backGroundColor.set() + UIRectFill(newImageFrame) + UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: corner).addClip() + self.draw(in: newImageFrame) + let result = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return result + } +} + +extension CGSize { + func fillingSize(toSize: CGSize) -> CGSize { + let aspectWidth = round(aspectRatio * toSize.height) + let aspectHeight = round(toSize.width / aspectRatio) + return aspectWidth < toSize.width ? + CGSize(width: toSize.width, height: aspectHeight) : + CGSize(width: aspectWidth, height: toSize.height) + } + func constrainedSize(toSize: CGSize) -> CGSize { + let aspectWidth = round(aspectRatio * toSize.height) + let aspectHeight = round(toSize.width / aspectRatio) + return aspectWidth > toSize.width ? + CGSize(width: toSize.width, height: aspectHeight) : + CGSize(width: aspectWidth, height: toSize.height) + } + private var aspectRatio: CGFloat { + return height == 0.0 ? 1.0 : width / height + } +} + + +var keyHitThstEdgeInsets = "HitTestEdgeInsets" +extension UIControl { + + public var lp_hitEdgeInsets: UIEdgeInsets? { + get { + let value = objc_getAssociatedObject(self, &keyHitThstEdgeInsets) as? NSValue + return value.map { $0.uiEdgeInsetsValue } + } + set { + guard newValue != nil else { return } + objc_setAssociatedObject(self, &keyHitThstEdgeInsets, NSValue(uiEdgeInsets: newValue!), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + + open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + if self.lp_hitEdgeInsets == nil || !self.isEnabled || self.isHidden { + return super.point(inside: point, with: event) + } + + let relativeFrame = self.bounds + let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.lp_hitEdgeInsets!) + return hitFrame.contains(point) + } +} + +extension String { + + var isAvailableAlbum: Bool { + return !(isEmpty || contains("Hidden") || contains("已隐藏") || contains("Deleted") || contains("最近删除")) + } +} + + + + + + + diff --git a/LPAlbum/Others/LPAlbum.bundle/Images/circle_normal@2x.png b/LPAlbum/Others/LPAlbum.bundle/Images/circle_normal@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..ce57e48d46fdce67e89eb7a760b79ebffd18ac81 GIT binary patch literal 1540 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL|<~l0AZa85pY6 z7#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3-BeIx*fm;}a85w5HkpLPN znHdsM65;D(m7Jfemza{Dl&V*eTL4tez+h8h1!U%?mLw`vE(HYzo1&C7s~{IQsCFRFRw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+UU-@r)U$VeBcLbtdwuOzWTH?LS3VhGF}m(=3qqRfJl%=|nBkhzIT z`K2YcN=hJ$-~i&zlFT%OO?kyoZvj2150cS0)HBeBn+?=z0}{3JFUm{>+6*$^&d>&| z5=9JZkBvUaawNwRvD2 z858g9e0c4#V{YbkUUU4GoBk29wzf9*TmJd(p+in97B7Ch`o^^6lNDE`*vbUkoEEWn!ij`E5ubk6urd?gCYuMrHlN9V1 zy;_d_%D;kV0X`QQPX9@*skx)wrS`Gzyz9X=iCLSioOlvgJ9h5fJ6DN4^rG$@PLnC- z!otGSgI8Yh*E*C?m5^{CS*z$+yU^@EN4v~^tQ1ugGrVYi!ckASI$w3#^y%@ziP_oN zfoXktCnt$r?7t|$lO!}-#%6}9Kyh)g?aiENM$X@4&i+>u7dpuFO}S`7_Z+3`k3Rm8 zV6}N`)bVcP^w^x!+1g@x$E%AHz3sGY-7MJn_-2LsZkAhAv|;i$*2ywYl+<^cR(zSk zY_V`Y!#fqmU8b}5TI3uonbbWe;^T5Fr5nsziicbSQeSE=3OnkdnxMY2GjpAj*lBsy zWAEPO&1cgW`oh-!NRs{Ho!wG0D-s2BiY5x*DU|*A%jQHsQS}p4dSHGg`y>AYvHVC9Ut$eYAFK5RF`S7bJH2+NGo?QNL_u`iv{n8(f zwdO2)E3!kBb?1rB*E22@il$lIx^w5wsj953Re86SZX9BDZ{l(BuaG#WC6IY7K&IDi z_f9S$vzzNe1%+&Rmc{NGqhWO9Q%8{eY>9&W{5rrgKFbc!ad{$Z%}P_wS@x97h1 z#XjmYR02-Otc&C9eA2GU$!T$QWxAB!OoM6ZRRskfm}HyxT+@uq$;fyysWWy*p~A{3 zQ`r90?VsMIW8-qc`b$iJJD<$kjz-Qt+wT!F6_9u7R=Us&GlZybAlzw@`*9txiy zy)HZa%jB`eqBYyLZTlv+`q!Q3pJjiUEbi4RSoC9p3*Ym@a$z-docd4tFg;Q24`!28 z+cnYSNt`M$zr=nGIO)}4v{-E3^W0~j|D;WQ{?pthsy%t*jW>bwpZ{mbkj|Lp^T!8N PIx~2>`njxgN@xNAT!nr$ literal 0 HcmV?d00001 diff --git a/LPAlbum/Others/LPAlbum.bundle/Images/circle_normal@3x.png b/LPAlbum/Others/LPAlbum.bundle/Images/circle_normal@3x.png new file mode 100755 index 0000000000000000000000000000000000000000..4ab33b6ad50493d8565af0b5d5a5aaadd2b9ccce GIT binary patch literal 3814 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 literal 0 HcmV?d00001 diff --git a/LPAlbum/Others/LPAlbum.bundle/Images/circle_selected@2x.png b/LPAlbum/Others/LPAlbum.bundle/Images/circle_selected@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..254c97770ef2274edf152151116db959f22ac414 GIT binary patch literal 4042 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 Hu0mjfps<$4 literal 0 HcmV?d00001 diff --git a/LPAlbum/Others/LPAlbum.bundle/Images/image_camera@3x.png b/LPAlbum/Others/LPAlbum.bundle/Images/image_camera@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4aa8c5ece7c8b2666e9d07b0d70401cf6fb8cc11 GIT binary patch literal 1326 zcmZ8hX;4#V6n!Cqu!hAcgY5enfvFWrEIVPb9RiW9?SMcaf)EKyLZVO}va|?-1G1+q z3KUQfD&PPj5eA7ZWg9dNT7zt|H?mVm+7C0sv^_KLe&?QZ?)~2T^KyK=JrrcsWg!Ss z@bYvcf|GsxNFzawlwh5MAcU^ZSAKZVCM6}&Xfzmx#RPCM7z~D(0831$R4O1RF$#qu z*6DQm|A;sPDgf96aIyp_$;rv!UN8a%0YEDr2bNe7TcCm)fdYs)lbDzYctS$Li3mo3 z0xE!51T8>(d_1URG8u@#0$l9=n;+*s0xSe4_#7sa2?$66fV)80J}$WwylZF@(fcBl z_P2ac`HPhU!^$1$oWD*FW9QV+90P^zz>3wLq8K+zePyZLpQ%;|(X%kc{Sv)f>NDOC zm15U%v*xd~G}q49Qd(3C9{;|f@<@`lWi*;k_&`1?jbiKBM!S&3Jw>*j@Jn|sI$Zwt(r zjt!tG+#~(MS2Rs?tT__G!1-^(B~Xh>jeyxrn+3-_gdr03(x;&WYyB&k_tu#|EQ#dDF(6 zt5bAm<9jwT32L3Lx2GEGZ>ZVnuv6G*>3vvHvvhS`#CZDlPwrNHf3Vdsx|80rfRo3E zF)oqG4K;Jt^4aC(%R+wm%JsaP+{uX}q2<%(CGS)bDIzCp6wy>oaCjA|`v9L5ROivr zHVa2#F|TLa8wa3E`ZB|XGS$scnr7?G!(vq3BsCaUudQ2@<;2Sihodkz7VerOMo0DD z9F*hW+%H+<3t8sy{TSBNPO&=tn8^wfwaCIQ$|iwW0aCkkrgoAZ6+JqZtdQFEd2@puf>JiM zyt`5HYLy;JtW*?WF(hI7PU!ge?fYkK*h&1_kKSq&BiMFrF3|I8K4cUGI>a zoF0Y*8Qab8>C@LG3#$=IQ1sF}DV0zN^@6&^v!B_@&dbgb7%<+SQbnchBd$Ia;Q?00whWBrx%jSOj62@0=C<1%V4ljmkM7P@ogJo{W6 zW|4!9IoP&4ZDr?sSY5R;v1XJNf+*kv$wCk_G4@AMKUa&2$}iL0)cS%<8`t_l9W|gm zYx5hsh{Lqm7-lxmq~BWV{4)NNyTPy=6=BJioR{=s z;{8KZO1K>hcqY?7EJT&Zr7dPPrAAUh)ZcJz_J(+=G-iOd&iLN(Z|@`IDV#l;!?Hrn Oap#5ic58JBz3~sTL1rWX literal 0 HcmV?d00001 diff --git a/LPAlbum/Others/LPAlbum.bundle/Images/meun_down@2x.png b/LPAlbum/Others/LPAlbum.bundle/Images/meun_down@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..43b7772511c9605cbde2c60b77f94e856b44d92b GIT binary patch literal 281 zcmV+!0p|XRP)Px#(@8`@R45gd%)bjkVHC#kI}pXdz``n%Oft$StE|c(f0)RqjIv2m$iiZ>FfdR= zxV{e(xo$l5d4HYr9>X-v8hQvBe}w)p`luKzV<-~@ewjmG5K|~a4T)l@8yUc7H;B3) zBPa_*Zv7nMSVQT+V>O6(FS<}xh`sgkki-^B3$E7&?ryZ9Y>;@ZKhG4>*h6W+p(48z zO(;90o}a7^G-PpvQiTT8bs-02tp0tx(U`{>%IvPx$V@X6oR5%f>ls!lTK@dhygP=r%3c*6s1S{>tP6V+@t=I-lDOd~kA$_nGv=mE0 zun_FjO02|2Eg}|1h(!cNyu8ufFkJS`vB|*8%+7t^>|O3B2*a=h4^V}npijZ9Ww?Pe z$OQb=N z`leoQ&tfqFw@M&oxh+UgpwGjz7)|_;H#@8}r{La*Y})A%IvR+l;nA8*-gZd;S_pFR z6ftbY8aRM93wIIE?th-&V;E-OMH%HUD7uXF$3Y%p9Tk_>*~2vlb>)_ux~9df({QOY zQtoL@q8ifzyo=k!C%GVI@-Up6zD)-dey;__CD1S42kR9$0UgIIQi-l0PQ-u$$ko`8 zd#2-*MSnhB3|a-bPtmxbF%ox@UVR1@L0`=^>oKok-AoLYFZ~nS*00000NkvXXu0mjfkK(r6 literal 0 HcmV?d00001 diff --git a/LPAlbum/Others/Models.swift b/LPAlbum/Others/Models.swift new file mode 100644 index 0000000..9cb1be8 --- /dev/null +++ b/LPAlbum/Others/Models.swift @@ -0,0 +1,64 @@ +// +// Models.swift +// LPAlbum +// +// Created by 郜宇 on 2017/9/11. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import Foundation +import Photos + +struct AssetModel { + var asset: PHAsset + var isSelect: Bool +} + +struct AlbumModel { + var name: String + var cover: PHAsset + var count: Int + var assetModels: [AssetModel] + var selectCount: Int { return assetModels.filter{ $0.isSelect == true }.count } +} + +extension PHAsset { + var toAssetModel: AssetModel { return AssetModel(asset: self, isSelect: false) } +} + +extension PHFetchResult where ObjectType == PHAsset { + var toAssetModels: [AssetModel] { + var result = [AssetModel]() + enumerateObjects({ (asset, i, stop) in + result.append(asset.toAssetModel) + }) + return result + } +} + +extension Array where Iterator.Element == AlbumModel { + + func change(assetModel: AssetModel) -> [AlbumModel] { + + return map{ + var albumModel = $0 + albumModel.assetModels = $0.assetModels.map{ + return $0.asset.localIdentifier == assetModel.asset.localIdentifier ? assetModel : $0 + } + return albumModel + } + } + + var allSelectedAssetModels: [AssetModel] { + return self[0].assetModels.filter{ $0.isSelect == true } + } + var allSelectedAsset: [PHAsset] { + return allSelectedAssetModels.map{ $0.asset } + } +} + + + + + + diff --git a/LPAlbum/Views/AlbumCollectionCell.swift b/LPAlbum/Views/AlbumCollectionCell.swift new file mode 100644 index 0000000..543c7c0 --- /dev/null +++ b/LPAlbum/Views/AlbumCollectionCell.swift @@ -0,0 +1,61 @@ +// +// AlbumCollectionCell.swift +// LPAlbum +// +// Created by 郜宇 on 2017/9/11. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import UIKit + +class AlbumCollectionCell: UICollectionViewCell { + + let photoView = UIImageView() + let iconButton = UIButton() + + var assetIdentifier: String! + + var iconClickAction: ((Bool) -> Void)? + + func set(_ assetModel: AssetModel){ + self.assetIdentifier = assetModel.asset.localIdentifier + AlbumManager.getPhoto(asset: assetModel.asset, targetSize: bounds.size) {[weak self] (image, _) in + guard let `self` = self else { return } + guard self.assetIdentifier == assetModel.asset.localIdentifier else { return } + self.photoView.image = image + } + iconButton.isSelected = assetModel.isSelect + } + + override init(frame: CGRect) { + super.init(frame: frame) + photoView.contentMode = .scaleAspectFill + photoView.clipsToBounds = true + photoView.frame = CGRect(origin: .zero, size: frame.size) + + let padding = 5.cgFloat + let iconWH = 20.adapt + + iconButton.frame = CGRect(x: frame.width - padding - iconWH, y: padding, width: iconWH, height: iconWH) + iconButton.setBackgroundImage(LPAlbum.Config.normalBox , for: .normal) + iconButton.setBackgroundImage(LPAlbum.Config.selectedBox, for: .selected) + iconButton.lp_hitEdgeInsets = UIEdgeInsets(top: -5, left: -5, bottom: -5, right: -5) + + addSubview(photoView) + addSubview(iconButton) + + iconButton.addTarget(self, action: #selector(iconTap), for: .touchUpInside) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func prepareForReuse() { + super.prepareForReuse() + photoView.image = nil + } + + func iconTap() { iconClickAction?(iconButton.isSelected) } +} + diff --git a/LPAlbum/Views/DropMenuView.swift b/LPAlbum/Views/DropMenuView.swift new file mode 100644 index 0000000..fc568cf --- /dev/null +++ b/LPAlbum/Views/DropMenuView.swift @@ -0,0 +1,151 @@ +// +// DropMenuView.swift +// MaiDou +// +// Created by 郜宇 on 2017/8/22. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import UIKit +import Photos + +class DropMenuView: UIView { + + fileprivate let tableView = UITableView() + fileprivate let backgroundView = UIView() + fileprivate var albums = [AlbumModel]() + + var maskViewClick: ((DropMenuView) -> Void)? + var menuViewDidSelect: ((Int) -> Void)? + + func show(begin: (() -> Void)? = nil, complete: (() -> Void)? = nil) { + begin?() + alpha = 1.0 + UIView.animate(withDuration: 0.35, animations: { + self.tableView.transform = CGAffineTransform(translationX: 0, y: self.tableView.bounds.height) + self.backgroundView.alpha = 1.0 + }) { (_) in + complete?() + } + } + + func dismiss(begin: (() -> Void)? = nil, complete: (() -> Void)? = nil) { + begin?() + UIView.animate(withDuration: 0.35, animations: { + self.tableView.transform = .identity + self.backgroundView.alpha = 0.0 + }) { (_) in + self.alpha = 0 + complete?() + } + } + + init(frame: CGRect, albums: [AlbumModel]) { + super.init(frame: frame) + + let rowHeight: CGFloat = 65 + + self.albums = albums + tableView.delegate = self + tableView.dataSource = self + alpha = 0 + + backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.5) + backgroundView.alpha = 0 + let tap = UITapGestureRecognizer(target: self, action: #selector(maskClick)) + backgroundView.addGestureRecognizer(tap) + + addSubview(backgroundView) + addSubview(tableView) + + tableView.register(MenuCell.self, forCellReuseIdentifier: MenuCell.description()) + tableView.separatorColor = UIColor.groupTableViewBackground + tableView.separatorInset = .zero + tableView.rowHeight = rowHeight + + let height = min(rowHeight * CGFloat(albums.count), 400) + tableView.frame = CGRect(x: 0, y: -height, width: frame.size.width, height: height) + backgroundView.frame = frame + } + + @objc private func maskClick() { dismiss(); maskViewClick?(self) } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension DropMenuView: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return albums.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: MenuCell.description(), for: indexPath) as! MenuCell + cell.album = albums[indexPath.row] + return cell + } + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + (cell as! MenuCell).album = albums[indexPath.row] + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + dismiss() + menuViewDidSelect?(indexPath.row) + } +} + + +final class MenuCell: UITableViewCell { + + private let titleLabel = UILabel() + private let iconView = UIImageView() + + var album: AlbumModel! { + didSet{ + titleLabel.text = album.name + " \(album.count)" + AlbumManager.getPhoto(asset: album.cover, targetSize: iconView.frame.size) {[weak self] (image, _) in + self?.iconView.image = image + } + layoutIfNeeded() + } + } + + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + titleLabel.textColor = UIColor.black + titleLabel.font = UIFont.systemFont(ofSize: 17) + titleLabel.lineBreakMode = .byTruncatingMiddle + + iconView.clipsToBounds = true + iconView.contentMode = .scaleAspectFill + + contentView.addSubview(titleLabel) + contentView.addSubview(iconView) + } + + override func layoutSubviews() { + super.layoutSubviews() + let padding: CGFloat = 10.0 + titleLabel.sizeToFit() + titleLabel.bounds = CGRect(x: 0, y: 0, + width: min(bounds.width - iconView.frame.maxX - 2 * padding, titleLabel.frame.width), + height: titleLabel.frame.height) + iconView.bounds = CGRect(x: 0, y: 0, width: 55, height: 55) + iconView.center = CGPoint(x: 40, y: bounds.height * 0.5) + titleLabel.center = CGPoint(x: titleLabel.frame.width * 0.5 + iconView.frame.maxX + padding, y: bounds.height * 0.5) + } + + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + + + + diff --git a/LPAlbum/Views/TakeCameraCell.swift b/LPAlbum/Views/TakeCameraCell.swift new file mode 100644 index 0000000..fb4b491 --- /dev/null +++ b/LPAlbum/Views/TakeCameraCell.swift @@ -0,0 +1,30 @@ +// +// TakeCameraCell.swift +// LPAlbum +// +// Created by 郜宇 on 2017/9/12. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import UIKit + +class TakeCameraCell: UICollectionViewCell { + + private let iconView = UIImageView(image: Bundle.imageFromBundle("image_camera")) + + override init(frame: CGRect) { + super.init(frame: frame) + contentView.backgroundColor = .black + contentView.addSubview(iconView) + } + + override func layoutSubviews() { + super.layoutSubviews() + iconView.bounds = bounds.applying(CGAffineTransform(scaleX: 0.5, y: 0.5)) + iconView.center = CGPoint(x: bounds.width * 0.5, y: bounds.height * 0.5) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/LPAlbum/Views/TitleView.swift b/LPAlbum/Views/TitleView.swift new file mode 100644 index 0000000..70c8341 --- /dev/null +++ b/LPAlbum/Views/TitleView.swift @@ -0,0 +1,52 @@ +// +// SessionTitleView.swift +// MaiDou +// +// Created by 郜宇 on 2017/8/22. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import UIKit + +class TitleView: UIButton { + + override var isSelected: Bool { + didSet{ + UIView.animate(withDuration: 0.25) { + self.imageView?.transform = self.isSelected ? CGAffineTransform(rotationAngle: CGFloat.pi) : .identity + } + } + } + + var title: String! { + didSet{ + setTitle(title, for: .normal) + sizeToFit() + } + } + + var clickAction: ((TitleView) -> Void)? + + override init(frame: CGRect) { + super.init(frame: frame) + setImage(LPAlbum.Config.arrowImage, for: .normal) + setTitleColor(LPAlbum.Config.barTitleColor, for: .normal) + titleLabel?.font = UIFont.systemFont(ofSize: 17) + bounds = CGRect(origin: .zero, size: CGSize(width: 200, height: 40)) + addTarget(self, action: #selector(click), for: .touchUpInside) + } + + func click() { clickAction?(self) } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + imageEdgeInsets = UIEdgeInsets(top: 0, left: (titleLabel?.frame.width ?? 0) + 5, bottom: 0, right: -(titleLabel?.frame.width ?? 0)) + titleEdgeInsets = UIEdgeInsets(top: 0, left: -(imageView?.frame.width ?? 0), bottom: 0, right: (imageView?.frame.width ?? 0) + 5) + + contentHorizontalAlignment = .center + } +} diff --git a/LPAlbumDemo/AlbumViewController.swift b/LPAlbumDemo/AlbumViewController.swift new file mode 100644 index 0000000..82fc7b5 --- /dev/null +++ b/LPAlbumDemo/AlbumViewController.swift @@ -0,0 +1,18 @@ +// +// AlbumViewController.swift +// LPAlbum +// +// Created by 郜宇 on 2017/9/8. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import UIKit + +class AlbumViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = UIColor.red + } +} diff --git a/LPAlbumDemo/AppDelegate.swift b/LPAlbumDemo/AppDelegate.swift new file mode 100644 index 0000000..78902c1 --- /dev/null +++ b/LPAlbumDemo/AppDelegate.swift @@ -0,0 +1,22 @@ +// +// AppDelegate.swift +// LPAlbumDemo +// +// Created by 郜宇 on 2017/9/8. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import UIKit +import LPAlbum + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + + return true + } +} + diff --git a/LPAlbumDemo/Assets.xcassets/AppIcon.appiconset/Contents.json b/LPAlbumDemo/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..b8236c6 --- /dev/null +++ b/LPAlbumDemo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,48 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/LPAlbumDemo/Base.lproj/LaunchScreen.storyboard b/LPAlbumDemo/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..fdf3f97 --- /dev/null +++ b/LPAlbumDemo/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LPAlbumDemo/Base.lproj/Main.storyboard b/LPAlbumDemo/Base.lproj/Main.storyboard new file mode 100644 index 0000000..3e70959 --- /dev/null +++ b/LPAlbumDemo/Base.lproj/Main.storyboard @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LPAlbumDemo/Info.plist b/LPAlbumDemo/Info.plist new file mode 100644 index 0000000..164f997 --- /dev/null +++ b/LPAlbumDemo/Info.plist @@ -0,0 +1,44 @@ + + + + + NSCameraUsageDescription + 获取相机权限 + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CFBundleAllowMixedLocalizations + + NSPhotoLibraryUsageDescription + 获取相册权限 + + diff --git a/LPAlbumDemo/PhotoCell.swift b/LPAlbumDemo/PhotoCell.swift new file mode 100644 index 0000000..66b5a40 --- /dev/null +++ b/LPAlbumDemo/PhotoCell.swift @@ -0,0 +1,28 @@ +// +// PhotoCell.swift +// LPAlbum +// +// Created by 郜宇 on 2017/9/13. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import UIKit + +class PhotoCell: UICollectionViewCell { + + let photoView = UIImageView() + + override init(frame: CGRect) { + super.init(frame: frame) + + photoView.frame = bounds + photoView.clipsToBounds = true + photoView.contentMode = .scaleAspectFill + contentView.addSubview(photoView) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/LPAlbumDemo/ViewController.swift b/LPAlbumDemo/ViewController.swift new file mode 100644 index 0000000..9490e81 --- /dev/null +++ b/LPAlbumDemo/ViewController.swift @@ -0,0 +1,87 @@ +// +// ViewController.swift +// LPAlbumDemo +// +// Created by 郜宇 on 2017/9/8. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import UIKit +import LPAlbum + +class ViewController: UIViewController { + + fileprivate var collectionView: UICollectionView! + fileprivate var photos = [UIImage]() + + override func viewDidLoad() { + super.viewDidLoad() + title = "LPAlbum" + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(openAlbum)) + navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(clean)) + + let layout = UICollectionViewFlowLayout() + let wh = view.bounds.width / 4.0 + layout.itemSize = CGSize(width: wh, height: wh) + layout.minimumLineSpacing = 0 + layout.minimumInteritemSpacing = 0 + collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.backgroundColor = .white + view.addSubview(collectionView) + collectionView.register(PhotoCell.self, forCellWithReuseIdentifier: PhotoCell.description()) + } + + func openAlbum() { + LPAlbum.show(at: self) { + $0.columnCount = 4 + $0.hasCamera = true + $0.maxSelectCount = 9 - self.photos.count + }.targeSize({ (size) -> CGSize in + return CGSize(width: 80, height: 80) + }).error {(vc, error) in + vc.show(message: error.localizedDescription) + }.complete { [weak self](images) in + self?.photos.append(contentsOf: images) + self?.collectionView.reloadData() + } + } + func clean() { + photos.removeAll() + collectionView.reloadData() + } +} + +extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return photos.count + } + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoCell.description(), for: indexPath) as! PhotoCell + cell.photoView.image = photos[indexPath.row] + return cell + } +} + + +extension UIViewController { + func show(message: String){ + let controler = UIAlertController(title: "提示", message: message, preferredStyle: .alert) + controler.addAction(UIAlertAction(title: "确定", style: .cancel, handler: nil)) + self.present(controler, animated: true, completion: nil) + } +} + + + + + + + + + + + + diff --git a/LPAlbumTests/Info.plist b/LPAlbumTests/Info.plist new file mode 100644 index 0000000..6c6c23c --- /dev/null +++ b/LPAlbumTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/LPAlbumTests/LPAlbumTests.swift b/LPAlbumTests/LPAlbumTests.swift new file mode 100644 index 0000000..2f7ade0 --- /dev/null +++ b/LPAlbumTests/LPAlbumTests.swift @@ -0,0 +1,36 @@ +// +// LPAlbumTests.swift +// LPAlbumTests +// +// Created by 郜宇 on 2017/9/8. +// Copyright © 2017年 Loopeer. All rights reserved. +// + +import XCTest +@testable import LPAlbum + +class LPAlbumTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +}