diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95f01fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,65 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +.DS_Store + +## Build generated +build/ +DerivedData + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.xccheckout +*.moved-aside +*.xcuserstate +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa + +## 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/ +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# 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://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md + +fastlane/report.xml +fastlane/screenshots diff --git a/NohanaImagePicker.xcodeproj/project.pbxproj b/NohanaImagePicker.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e4ad533 --- /dev/null +++ b/NohanaImagePicker.xcodeproj/project.pbxproj @@ -0,0 +1,736 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + F202573A1C7343D20069B33A /* ImageName.swift in Sources */ = {isa = PBXBuildFile; fileRef = F20257391C7343D20069B33A /* ImageName.swift */; }; + F202573C1C7418920069B33A /* PickedAssetList.swift in Sources */ = {isa = PBXBuildFile; fileRef = F202573B1C7418920069B33A /* PickedAssetList.swift */; }; + F2131F431C79615700797887 /* SwipeInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2131F421C79615700797887 /* SwipeInteractionController.swift */; }; + F218D7D61C6B3D22001FCED1 /* PhotoKitAlbumList.swift in Sources */ = {isa = PBXBuildFile; fileRef = F218D7D51C6B3D22001FCED1 /* PhotoKitAlbumList.swift */; }; + F218D7DE1C6C3A5B001FCED1 /* AlbumCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F218D7DD1C6C3A5B001FCED1 /* AlbumCell.swift */; }; + F23554261C69D19C00796DCA /* NohanaImagePicker.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F23554251C69D19C00796DCA /* NohanaImagePicker.storyboard */; }; + F23554281C69D5DB00796DCA /* NohanaImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F23554271C69D5DB00796DCA /* NohanaImagePickerController.swift */; }; + F237249B1C6DCF96005D1E8A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F237249A1C6DCF96005D1E8A /* Images.xcassets */; }; + F23CC89E1CB745C800BCE443 /* ActivityIndicatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F23CC89D1CB745C800BCE443 /* ActivityIndicatable.swift */; }; + F24EB6901C68AEED0002EC86 /* AlbumListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24EB68F1C68AEED0002EC86 /* AlbumListViewController.swift */; }; + F24EB6911C68AFB40002EC86 /* NohanaImagePicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F2C08D741C68651900B00181 /* NohanaImagePicker.framework */; }; + F24EB6921C68AFB40002EC86 /* NohanaImagePicker.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F2C08D741C68651900B00181 /* NohanaImagePicker.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + F25C10C21C8ED9BF007453C3 /* MomentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F25C10C11C8ED9BF007453C3 /* MomentViewController.swift */; }; + F25C69881CA23A0A005935D6 /* MomentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F25C69871CA23A0A005935D6 /* MomentCell.swift */; }; + F25C69901CA27311005935D6 /* EmptyIndicatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F25C698F1CA27311005935D6 /* EmptyIndicatable.swift */; }; + F25C69921CA28728005935D6 /* AlbumListEmptyIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F25C69911CA28728005935D6 /* AlbumListEmptyIndicator.swift */; }; + F25C699B1CA2B79A005935D6 /* ColorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = F25C699A1CA2B79A005935D6 /* ColorConfig.swift */; }; + F26775DE1C701FA7002E786C /* ItemListType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F26775DD1C701FA7002E786C /* ItemListType.swift */; }; + F26775E11C7046C7002E786C /* ExpandingAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F26775E01C7046C7002E786C /* ExpandingAnimationController.swift */; }; + F26775E51C70574F002E786C /* AnimatableNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F26775E41C70574F002E786C /* AnimatableNavigationController.swift */; }; + F26775E81C7073CD002E786C /* Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = F26775E71C7073CD002E786C /* Size.swift */; }; + F26775EA1C71645A002E786C /* ContractingAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F26775E91C71645A002E786C /* ContractingAnimationController.swift */; }; + F27029CB1C71C43A001647AB /* NohanaImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = F27029CD1C71C43A001647AB /* NohanaImagePicker.strings */; }; + F28F4AC31C6C49EE00B7D725 /* PhotoKitAssetList.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28F4AC21C6C49EE00B7D725 /* PhotoKitAssetList.swift */; }; + F28F4AC51C6C59A500B7D725 /* PhotoKitAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28F4AC41C6C59A500B7D725 /* PhotoKitAsset.swift */; }; + F2C08D781C68651900B00181 /* NohanaImagePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = F2C08D771C68651900B00181 /* NohanaImagePicker.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F2C08D951C688FC400B00181 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C08D941C688FC400B00181 /* AppDelegate.swift */; }; + F2C08D971C688FC400B00181 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C08D961C688FC400B00181 /* ViewController.swift */; }; + F2C08D9A1C688FC400B00181 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F2C08D981C688FC400B00181 /* Main.storyboard */; }; + F2C08D9C1C688FC400B00181 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F2C08D9B1C688FC400B00181 /* Assets.xcassets */; }; + F2C08D9F1C688FC400B00181 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F2C08D9D1C688FC400B00181 /* LaunchScreen.storyboard */; }; + F2C08DAA1C688FC400B00181 /* NohanaImagePickerSampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C08DA91C688FC400B00181 /* NohanaImagePickerSampleTests.swift */; }; + F2DA29771C7749D600B0A8E3 /* NotificationInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2DA29761C7749D600B0A8E3 /* NotificationInfo.swift */; }; + F2DF3B151C6C76E500C1C0E4 /* AssetListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2DF3B141C6C76E500C1C0E4 /* AssetListViewController.swift */; }; + F2DF3B171C6C868E00C1C0E4 /* AssetCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2DF3B161C6C868E00C1C0E4 /* AssetCell.swift */; }; + F2DF3B2B1C6CC4DB00C1C0E4 /* AssetDetailListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2DF3B2A1C6CC4DB00C1C0E4 /* AssetDetailListViewController.swift */; }; + F2DF3B2D1C6D780100C1C0E4 /* AssetDetailCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2DF3B2C1C6D780100C1C0E4 /* AssetDetailCell.swift */; }; + F2FE1F781C901D9400FDBE7B /* MomentSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2FE1F761C901D9400FDBE7B /* MomentSectionHeaderView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + F24EB6931C68AFB40002EC86 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F2C08D6B1C68651900B00181 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F2C08D731C68651900B00181; + remoteInfo = NohanaImagePicker; + }; + F2C08DA61C688FC400B00181 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F2C08D6B1C68651900B00181 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F2C08D911C688FC400B00181; + remoteInfo = NohanaImagePickerSample; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + F24EB6951C68AFB40002EC86 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + F24EB6921C68AFB40002EC86 /* NohanaImagePicker.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + F20257391C7343D20069B33A /* ImageName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageName.swift; sourceTree = ""; }; + F202573B1C7418920069B33A /* PickedAssetList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickedAssetList.swift; sourceTree = ""; }; + F2131F421C79615700797887 /* SwipeInteractionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipeInteractionController.swift; sourceTree = ""; }; + F218D7D51C6B3D22001FCED1 /* PhotoKitAlbumList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoKitAlbumList.swift; sourceTree = ""; }; + F218D7DD1C6C3A5B001FCED1 /* AlbumCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumCell.swift; sourceTree = ""; }; + F23554251C69D19C00796DCA /* NohanaImagePicker.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NohanaImagePicker.storyboard; sourceTree = ""; }; + F23554271C69D5DB00796DCA /* NohanaImagePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NohanaImagePickerController.swift; sourceTree = ""; }; + F237249A1C6DCF96005D1E8A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + F23CC89D1CB745C800BCE443 /* ActivityIndicatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicatable.swift; sourceTree = ""; }; + F24EB68F1C68AEED0002EC86 /* AlbumListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumListViewController.swift; sourceTree = ""; }; + F25C10C11C8ED9BF007453C3 /* MomentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MomentViewController.swift; sourceTree = ""; }; + F25C69871CA23A0A005935D6 /* MomentCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MomentCell.swift; sourceTree = ""; }; + F25C698F1CA27311005935D6 /* EmptyIndicatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyIndicatable.swift; sourceTree = ""; }; + F25C69911CA28728005935D6 /* AlbumListEmptyIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumListEmptyIndicator.swift; sourceTree = ""; }; + F25C699A1CA2B79A005935D6 /* ColorConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorConfig.swift; sourceTree = ""; }; + F26775DD1C701FA7002E786C /* ItemListType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListType.swift; sourceTree = ""; }; + F26775E01C7046C7002E786C /* ExpandingAnimationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpandingAnimationController.swift; sourceTree = ""; }; + F26775E41C70574F002E786C /* AnimatableNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatableNavigationController.swift; sourceTree = ""; }; + F26775E71C7073CD002E786C /* Size.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Size.swift; sourceTree = ""; }; + F26775E91C71645A002E786C /* ContractingAnimationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContractingAnimationController.swift; sourceTree = ""; }; + F27029CC1C71C43A001647AB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/NohanaImagePicker.strings; sourceTree = ""; }; + F27029CF1C71C4FE001647AB /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/NohanaImagePicker.strings; sourceTree = ""; }; + F28F4AC21C6C49EE00B7D725 /* PhotoKitAssetList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoKitAssetList.swift; sourceTree = ""; }; + F28F4AC41C6C59A500B7D725 /* PhotoKitAsset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoKitAsset.swift; sourceTree = ""; }; + F2C08D741C68651900B00181 /* NohanaImagePicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NohanaImagePicker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F2C08D771C68651900B00181 /* NohanaImagePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NohanaImagePicker.h; sourceTree = ""; }; + F2C08D791C68651900B00181 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F2C08D921C688FC400B00181 /* NohanaImagePickerSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NohanaImagePickerSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F2C08D941C688FC400B00181 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + F2C08D961C688FC400B00181 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + F2C08D991C688FC400B00181 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + F2C08D9B1C688FC400B00181 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + F2C08D9E1C688FC400B00181 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + F2C08DA01C688FC400B00181 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F2C08DA51C688FC400B00181 /* NohanaImagePickerSampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NohanaImagePickerSampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + F2C08DA91C688FC400B00181 /* NohanaImagePickerSampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NohanaImagePickerSampleTests.swift; sourceTree = ""; }; + F2C08DAB1C688FC400B00181 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F2DA29761C7749D600B0A8E3 /* NotificationInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationInfo.swift; sourceTree = ""; }; + F2DF3B141C6C76E500C1C0E4 /* AssetListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetListViewController.swift; sourceTree = ""; }; + F2DF3B161C6C868E00C1C0E4 /* AssetCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetCell.swift; sourceTree = ""; }; + F2DF3B2A1C6CC4DB00C1C0E4 /* AssetDetailListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDetailListViewController.swift; sourceTree = ""; }; + F2DF3B2C1C6D780100C1C0E4 /* AssetDetailCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDetailCell.swift; sourceTree = ""; }; + F2FE1F761C901D9400FDBE7B /* MomentSectionHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MomentSectionHeaderView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + F2C08D701C68651900B00181 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F2C08D8F1C688FC400B00181 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F24EB6911C68AFB40002EC86 /* NohanaImagePicker.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F2C08DA21C688FC400B00181 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + F218D7DC1C6C3A3E001FCED1 /* Views */ = { + isa = PBXGroup; + children = ( + F218D7DD1C6C3A5B001FCED1 /* AlbumCell.swift */, + F2DF3B161C6C868E00C1C0E4 /* AssetCell.swift */, + F2DF3B2C1C6D780100C1C0E4 /* AssetDetailCell.swift */, + F25C69871CA23A0A005935D6 /* MomentCell.swift */, + F2FE1F761C901D9400FDBE7B /* MomentSectionHeaderView.swift */, + F25C69911CA28728005935D6 /* AlbumListEmptyIndicator.swift */, + ); + name = Views; + sourceTree = ""; + }; + F235541F1C698BE900796DCA /* Resources */ = { + isa = PBXGroup; + children = ( + F23554251C69D19C00796DCA /* NohanaImagePicker.storyboard */, + F27029CD1C71C43A001647AB /* NohanaImagePicker.strings */, + F237249A1C6DCF96005D1E8A /* Images.xcassets */, + ); + name = Resources; + sourceTree = ""; + }; + F23554291C69F38900796DCA /* Photos */ = { + isa = PBXGroup; + children = ( + F218D7D51C6B3D22001FCED1 /* PhotoKitAlbumList.swift */, + F28F4AC21C6C49EE00B7D725 /* PhotoKitAssetList.swift */, + F28F4AC41C6C59A500B7D725 /* PhotoKitAsset.swift */, + ); + name = Photos; + sourceTree = ""; + }; + F26775DF1C704574002E786C /* AnimationController */ = { + isa = PBXGroup; + children = ( + F26775E41C70574F002E786C /* AnimatableNavigationController.swift */, + F26775E01C7046C7002E786C /* ExpandingAnimationController.swift */, + F26775E91C71645A002E786C /* ContractingAnimationController.swift */, + F2131F421C79615700797887 /* SwipeInteractionController.swift */, + ); + name = AnimationController; + sourceTree = ""; + }; + F26775E61C7073B1002E786C /* Common */ = { + isa = PBXGroup; + children = ( + F26775DD1C701FA7002E786C /* ItemListType.swift */, + F202573B1C7418920069B33A /* PickedAssetList.swift */, + F20257391C7343D20069B33A /* ImageName.swift */, + F26775E71C7073CD002E786C /* Size.swift */, + F2DA29761C7749D600B0A8E3 /* NotificationInfo.swift */, + F25C698F1CA27311005935D6 /* EmptyIndicatable.swift */, + F23CC89D1CB745C800BCE443 /* ActivityIndicatable.swift */, + F25C699A1CA2B79A005935D6 /* ColorConfig.swift */, + ); + name = Common; + sourceTree = ""; + }; + F2C08D6A1C68651900B00181 = { + isa = PBXGroup; + children = ( + F2C08D761C68651900B00181 /* NohanaImagePicker */, + F2C08D931C688FC400B00181 /* NohanaImagePickerSample */, + F2C08DA81C688FC400B00181 /* NohanaImagePickerSampleTests */, + F2C08D751C68651900B00181 /* Products */, + ); + sourceTree = ""; + }; + F2C08D751C68651900B00181 /* Products */ = { + isa = PBXGroup; + children = ( + F2C08D741C68651900B00181 /* NohanaImagePicker.framework */, + F2C08D921C688FC400B00181 /* NohanaImagePickerSample.app */, + F2C08DA51C688FC400B00181 /* NohanaImagePickerSampleTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + F2C08D761C68651900B00181 /* NohanaImagePicker */ = { + isa = PBXGroup; + children = ( + F2C08D771C68651900B00181 /* NohanaImagePicker.h */, + F2C08D791C68651900B00181 /* Info.plist */, + F26775E61C7073B1002E786C /* Common */, + F23554291C69F38900796DCA /* Photos */, + F218D7DC1C6C3A3E001FCED1 /* Views */, + F2E508901C68B1770082EF93 /* ViewControllers */, + F26775DF1C704574002E786C /* AnimationController */, + F235541F1C698BE900796DCA /* Resources */, + ); + path = NohanaImagePicker; + sourceTree = ""; + }; + F2C08D931C688FC400B00181 /* NohanaImagePickerSample */ = { + isa = PBXGroup; + children = ( + F2C08D941C688FC400B00181 /* AppDelegate.swift */, + F2C08D961C688FC400B00181 /* ViewController.swift */, + F2C08D981C688FC400B00181 /* Main.storyboard */, + F2C08D9B1C688FC400B00181 /* Assets.xcassets */, + F2C08D9D1C688FC400B00181 /* LaunchScreen.storyboard */, + F2C08DA01C688FC400B00181 /* Info.plist */, + ); + path = NohanaImagePickerSample; + sourceTree = ""; + }; + F2C08DA81C688FC400B00181 /* NohanaImagePickerSampleTests */ = { + isa = PBXGroup; + children = ( + F2C08DA91C688FC400B00181 /* NohanaImagePickerSampleTests.swift */, + F2C08DAB1C688FC400B00181 /* Info.plist */, + ); + path = NohanaImagePickerSampleTests; + sourceTree = ""; + }; + F2E508901C68B1770082EF93 /* ViewControllers */ = { + isa = PBXGroup; + children = ( + F23554271C69D5DB00796DCA /* NohanaImagePickerController.swift */, + F24EB68F1C68AEED0002EC86 /* AlbumListViewController.swift */, + F2DF3B141C6C76E500C1C0E4 /* AssetListViewController.swift */, + F25C10C11C8ED9BF007453C3 /* MomentViewController.swift */, + F2DF3B2A1C6CC4DB00C1C0E4 /* AssetDetailListViewController.swift */, + ); + name = ViewControllers; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + F2C08D711C68651900B00181 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + F2C08D781C68651900B00181 /* NohanaImagePicker.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + F2C08D731C68651900B00181 /* NohanaImagePicker */ = { + isa = PBXNativeTarget; + buildConfigurationList = F2C08D881C68651900B00181 /* Build configuration list for PBXNativeTarget "NohanaImagePicker" */; + buildPhases = ( + F2C08D6F1C68651900B00181 /* Sources */, + F2C08D701C68651900B00181 /* Frameworks */, + F2C08D711C68651900B00181 /* Headers */, + F2C08D721C68651900B00181 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NohanaImagePicker; + productName = NohanaImagePicker; + productReference = F2C08D741C68651900B00181 /* NohanaImagePicker.framework */; + productType = "com.apple.product-type.framework"; + }; + F2C08D911C688FC400B00181 /* NohanaImagePickerSample */ = { + isa = PBXNativeTarget; + buildConfigurationList = F2C08DAC1C688FC400B00181 /* Build configuration list for PBXNativeTarget "NohanaImagePickerSample" */; + buildPhases = ( + F2C08D8E1C688FC400B00181 /* Sources */, + F2C08D8F1C688FC400B00181 /* Frameworks */, + F2C08D901C688FC400B00181 /* Resources */, + F24EB6951C68AFB40002EC86 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + F24EB6941C68AFB40002EC86 /* PBXTargetDependency */, + ); + name = NohanaImagePickerSample; + productName = NohanaImagePickerSample; + productReference = F2C08D921C688FC400B00181 /* NohanaImagePickerSample.app */; + productType = "com.apple.product-type.application"; + }; + F2C08DA41C688FC400B00181 /* NohanaImagePickerSampleTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = F2C08DAF1C688FC400B00181 /* Build configuration list for PBXNativeTarget "NohanaImagePickerSampleTests" */; + buildPhases = ( + F2C08DA11C688FC400B00181 /* Sources */, + F2C08DA21C688FC400B00181 /* Frameworks */, + F2C08DA31C688FC400B00181 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + F2C08DA71C688FC400B00181 /* PBXTargetDependency */, + ); + name = NohanaImagePickerSampleTests; + productName = NohanaImagePickerSampleTests; + productReference = F2C08DA51C688FC400B00181 /* NohanaImagePickerSampleTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F2C08D6B1C68651900B00181 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0720; + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = nohana; + TargetAttributes = { + F2C08D731C68651900B00181 = { + CreatedOnToolsVersion = 7.2.1; + }; + F2C08D911C688FC400B00181 = { + CreatedOnToolsVersion = 7.2.1; + }; + F2C08DA41C688FC400B00181 = { + CreatedOnToolsVersion = 7.2.1; + TestTargetID = F2C08D911C688FC400B00181; + }; + }; + }; + buildConfigurationList = F2C08D6E1C68651900B00181 /* Build configuration list for PBXProject "NohanaImagePicker" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ja, + ); + mainGroup = F2C08D6A1C68651900B00181; + productRefGroup = F2C08D751C68651900B00181 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F2C08D731C68651900B00181 /* NohanaImagePicker */, + F2C08D911C688FC400B00181 /* NohanaImagePickerSample */, + F2C08DA41C688FC400B00181 /* NohanaImagePickerSampleTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + F2C08D721C68651900B00181 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F23554261C69D19C00796DCA /* NohanaImagePicker.storyboard in Resources */, + F27029CB1C71C43A001647AB /* NohanaImagePicker.strings in Resources */, + F237249B1C6DCF96005D1E8A /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F2C08D901C688FC400B00181 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F2C08D9F1C688FC400B00181 /* LaunchScreen.storyboard in Resources */, + F2C08D9C1C688FC400B00181 /* Assets.xcassets in Resources */, + F2C08D9A1C688FC400B00181 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F2C08DA31C688FC400B00181 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + F2C08D6F1C68651900B00181 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F2DF3B171C6C868E00C1C0E4 /* AssetCell.swift in Sources */, + F26775E81C7073CD002E786C /* Size.swift in Sources */, + F26775DE1C701FA7002E786C /* ItemListType.swift in Sources */, + F23CC89E1CB745C800BCE443 /* ActivityIndicatable.swift in Sources */, + F202573C1C7418920069B33A /* PickedAssetList.swift in Sources */, + F25C10C21C8ED9BF007453C3 /* MomentViewController.swift in Sources */, + F25C69881CA23A0A005935D6 /* MomentCell.swift in Sources */, + F2FE1F781C901D9400FDBE7B /* MomentSectionHeaderView.swift in Sources */, + F202573A1C7343D20069B33A /* ImageName.swift in Sources */, + F23554281C69D5DB00796DCA /* NohanaImagePickerController.swift in Sources */, + F25C69921CA28728005935D6 /* AlbumListEmptyIndicator.swift in Sources */, + F2131F431C79615700797887 /* SwipeInteractionController.swift in Sources */, + F2DF3B2D1C6D780100C1C0E4 /* AssetDetailCell.swift in Sources */, + F25C69901CA27311005935D6 /* EmptyIndicatable.swift in Sources */, + F2DA29771C7749D600B0A8E3 /* NotificationInfo.swift in Sources */, + F218D7DE1C6C3A5B001FCED1 /* AlbumCell.swift in Sources */, + F26775E11C7046C7002E786C /* ExpandingAnimationController.swift in Sources */, + F28F4AC51C6C59A500B7D725 /* PhotoKitAsset.swift in Sources */, + F2DF3B151C6C76E500C1C0E4 /* AssetListViewController.swift in Sources */, + F26775E51C70574F002E786C /* AnimatableNavigationController.swift in Sources */, + F2DF3B2B1C6CC4DB00C1C0E4 /* AssetDetailListViewController.swift in Sources */, + F218D7D61C6B3D22001FCED1 /* PhotoKitAlbumList.swift in Sources */, + F24EB6901C68AEED0002EC86 /* AlbumListViewController.swift in Sources */, + F25C699B1CA2B79A005935D6 /* ColorConfig.swift in Sources */, + F28F4AC31C6C49EE00B7D725 /* PhotoKitAssetList.swift in Sources */, + F26775EA1C71645A002E786C /* ContractingAnimationController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F2C08D8E1C688FC400B00181 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F2C08D971C688FC400B00181 /* ViewController.swift in Sources */, + F2C08D951C688FC400B00181 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F2C08DA11C688FC400B00181 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F2C08DAA1C688FC400B00181 /* NohanaImagePickerSampleTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + F24EB6941C68AFB40002EC86 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F2C08D731C68651900B00181 /* NohanaImagePicker */; + targetProxy = F24EB6931C68AFB40002EC86 /* PBXContainerItemProxy */; + }; + F2C08DA71C688FC400B00181 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F2C08D911C688FC400B00181 /* NohanaImagePickerSample */; + targetProxy = F2C08DA61C688FC400B00181 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + F27029CD1C71C43A001647AB /* NohanaImagePicker.strings */ = { + isa = PBXVariantGroup; + children = ( + F27029CC1C71C43A001647AB /* en */, + F27029CF1C71C4FE001647AB /* ja */, + ); + name = NohanaImagePicker.strings; + sourceTree = ""; + }; + F2C08D981C688FC400B00181 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + F2C08D991C688FC400B00181 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + F2C08D9D1C688FC400B00181 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + F2C08D9E1C688FC400B00181 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + F2C08D861C68651900B00181 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + 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_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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 = 9.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + F2C08D871C68651900B00181 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + 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_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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 = 9.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + F2C08D891C68651900B00181 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = NohanaImagePicker/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = jp.co.nohana.NohanaImagePicker; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + F2C08D8A1C68651900B00181 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = NohanaImagePicker/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = jp.co.nohana.NohanaImagePicker; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + F2C08DAD1C688FC400B00181 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + INFOPLIST_FILE = NohanaImagePickerSample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = jp.co.nohana.NohanaImagePickerSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + F2C08DAE1C688FC400B00181 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + INFOPLIST_FILE = NohanaImagePickerSample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = jp.co.nohana.NohanaImagePickerSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + F2C08DB01C688FC400B00181 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + INFOPLIST_FILE = NohanaImagePickerSampleTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = jp.co.nohana.NohanaImagePickerSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NohanaImagePickerSample.app/NohanaImagePickerSample"; + }; + name = Debug; + }; + F2C08DB11C688FC400B00181 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + INFOPLIST_FILE = NohanaImagePickerSampleTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = jp.co.nohana.NohanaImagePickerSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NohanaImagePickerSample.app/NohanaImagePickerSample"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + F2C08D6E1C68651900B00181 /* Build configuration list for PBXProject "NohanaImagePicker" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F2C08D861C68651900B00181 /* Debug */, + F2C08D871C68651900B00181 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F2C08D881C68651900B00181 /* Build configuration list for PBXNativeTarget "NohanaImagePicker" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F2C08D891C68651900B00181 /* Debug */, + F2C08D8A1C68651900B00181 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F2C08DAC1C688FC400B00181 /* Build configuration list for PBXNativeTarget "NohanaImagePickerSample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F2C08DAD1C688FC400B00181 /* Debug */, + F2C08DAE1C688FC400B00181 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F2C08DAF1C688FC400B00181 /* Build configuration list for PBXNativeTarget "NohanaImagePickerSampleTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F2C08DB01C688FC400B00181 /* Debug */, + F2C08DB11C688FC400B00181 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = F2C08D6B1C68651900B00181 /* Project object */; +} diff --git a/NohanaImagePicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/NohanaImagePicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..4e02c73 --- /dev/null +++ b/NohanaImagePicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/NohanaImagePicker.xcodeproj/xcshareddata/xcschemes/NohanaImagePicker.xcscheme b/NohanaImagePicker.xcodeproj/xcshareddata/xcschemes/NohanaImagePicker.xcscheme new file mode 100644 index 0000000..1159c40 --- /dev/null +++ b/NohanaImagePicker.xcodeproj/xcshareddata/xcschemes/NohanaImagePicker.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NohanaImagePicker.xcodeproj/xcshareddata/xcschemes/NohanaImagePickerSample.xcscheme b/NohanaImagePicker.xcodeproj/xcshareddata/xcschemes/NohanaImagePickerSample.xcscheme new file mode 100644 index 0000000..6086545 --- /dev/null +++ b/NohanaImagePicker.xcodeproj/xcshareddata/xcschemes/NohanaImagePickerSample.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NohanaImagePicker/ActivityIndicatable.swift b/NohanaImagePicker/ActivityIndicatable.swift new file mode 100644 index 0000000..aa8f9f9 --- /dev/null +++ b/NohanaImagePicker/ActivityIndicatable.swift @@ -0,0 +1,24 @@ +// +// ActivityIndicatable.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/04/08. +// Copyright © 2016年 nohana. All rights reserved. +// + +public protocol ActivityIndicatable { + func isProgressing() -> Bool + func updateVisibilityOfActivityIndicator(activityIndicator: UIView) +} + +public extension ActivityIndicatable where Self: UIViewController { + func updateVisibilityOfActivityIndicator(activityIndicator: UIView) { + if isProgressing() { + if !view.subviews.contains(activityIndicator) { + view.addSubview(activityIndicator) + } + } else { + activityIndicator.removeFromSuperview() + } + } +} \ No newline at end of file diff --git a/NohanaImagePicker/AlbumCell.swift b/NohanaImagePicker/AlbumCell.swift new file mode 100644 index 0000000..31cba4f --- /dev/null +++ b/NohanaImagePicker/AlbumCell.swift @@ -0,0 +1,14 @@ +// +// AlbumCell.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/11. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit + +class AlbumCell: UITableViewCell { + @IBOutlet weak var thumbnailImageView: UIImageView! + @IBOutlet weak var titleLabel: UILabel! +} diff --git a/NohanaImagePicker/AlbumListEmptyIndicator.swift b/NohanaImagePicker/AlbumListEmptyIndicator.swift new file mode 100644 index 0000000..1cfcf94 --- /dev/null +++ b/NohanaImagePicker/AlbumListEmptyIndicator.swift @@ -0,0 +1,43 @@ +// +// AlbumListEmptyIndicator.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/03/23. +// Copyright © 2016年 nohana. All rights reserved. +// + +class AlbumListEmptyIndicator: UILabel { + + init(message: String, description: String, frame: CGRect) { + super.init(frame: frame) + + let centerStyle = NSMutableParagraphStyle() + centerStyle.alignment = NSTextAlignment.Center + + let messageAttributes = [ + NSForegroundColorAttributeName : ColorConfig.emptyIndicator, + NSFontAttributeName : UIFont.systemFontOfSize(26), + NSParagraphStyleAttributeName : centerStyle + ] + let messageText = NSAttributedString(string: message, attributes: messageAttributes) + + let descriptionAttributes = [ + NSForegroundColorAttributeName : ColorConfig.emptyIndicator, + NSFontAttributeName : UIFont.systemFontOfSize(14), + NSParagraphStyleAttributeName : centerStyle + ] + let descriptionText = NSAttributedString(string: description, attributes: descriptionAttributes) + + let attributedText = NSMutableAttributedString() + attributedText.appendAttributedString(messageText) + attributedText.appendAttributedString(NSAttributedString(string: "\n\n", attributes: [NSFontAttributeName : UIFont.systemFontOfSize(6)])) + attributedText.appendAttributedString(descriptionText) + + self.numberOfLines = 0 + self.attributedText = attributedText + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} \ No newline at end of file diff --git a/NohanaImagePicker/AlbumListViewController.swift b/NohanaImagePicker/AlbumListViewController.swift new file mode 100644 index 0000000..03831ce --- /dev/null +++ b/NohanaImagePicker/AlbumListViewController.swift @@ -0,0 +1,253 @@ +// +// AlbumListViewController.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/08. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit +import Photos + +@available(iOS 8.0, *) +class AlbumListViewController: UITableViewController, EmptyIndicatable, ActivityIndicatable { + + weak var nohanaImagePickerController: NohanaImagePickerController? + var photoKitAlbumList: PhotoKitAlbumList! + + override func viewDidLoad() { + super.viewDidLoad() + title = NSLocalizedString("albumlist.title", tableName: "NohanaImagePicker", bundle: nohanaImagePickerController!.assetBundle, comment: "") + setUpToolbarItems() + navigationController?.setToolbarHidden(nohanaImagePickerController?.toolbarHidden ?? false, animated: false) + setUpEmptyIndicator() + setUpActivityIndicator() + self.view.backgroundColor = ColorConfig.backgroundColor + } + + deinit { + NSNotificationCenter.defaultCenter().removeObserver(self) + } + + override func viewWillAppear(animated: Bool) { + super.viewWillAppear(animated) + setToolbarTitle(nohanaImagePickerController) + if let indexPathForSelectedRow = tableView.indexPathForSelectedRow { + tableView.deselectRowAtIndexPath(indexPathForSelectedRow, animated: true) + } + } + + // MARK: - UITableViewDelegate + + override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + guard let nohanaImagePickerController = nohanaImagePickerController else { + return + } + nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, didSelectPhotoKitAssetList: photoKitAlbumList[exactRow(indexPath)].assetList) + } + + override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { + guard let nohanaImagePickerController = nohanaImagePickerController else { + return 0 + } + if nohanaImagePickerController.shouldShowMoment && indexPath.row == 0 { + return 52 + } + return 82 + } + + // MARK: - UITableViewDataSource + + override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if let emptyIndicator = emptyIndicator { + updateVisibilityOfEmptyIndicator(emptyIndicator) + } + if let activityIndicator = activityIndicator { + updateVisibilityOfActivityIndicator(activityIndicator) + } + + guard nohanaImagePickerController?.shouldShowMoment ?? false else { + return photoKitAlbumList.count + } + return photoKitAlbumList.count != 0 ? photoKitAlbumList.count + 1 : 0 + } + + override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + guard !isMomentRow(indexPath) else { + guard let cell = tableView.dequeueReusableCellWithIdentifier("MomentAlbumCell") as? AlbumCell else { + fatalError("failed to dequeueReusableCellWithIdentifier(\"MomentAlbumCell\")") + } + cell.titleLabel?.text = NSLocalizedString("albumlist.moment.title", tableName: "NohanaImagePicker", bundle: nohanaImagePickerController!.assetBundle, comment: "") + return cell + } + + guard let cell = tableView.dequeueReusableCellWithIdentifier("AlbumCell") as? AlbumCell else { + fatalError("failed to dequeueReusableCellWithIdentifier(\"AlbumCell\")") + } + let albumList = photoKitAlbumList[exactRow(indexPath)] + cell.titleLabel.text = albumList.title + cell.tag = exactRow(indexPath) + let imageSize = CGSize( + width: cell.thumbnailImageView.frame.size.width * UIScreen.mainScreen().scale, + height: cell.thumbnailImageView.frame.size.width * UIScreen.mainScreen().scale + ) + if let lastAsset = albumList.last { + lastAsset.image(imageSize, handler: { (imageData) -> Void in + dispatch_async(dispatch_get_main_queue(), { () -> Void in + if let imageData = imageData { + if cell.tag == self.exactRow(indexPath) { + cell.thumbnailImageView.image = imageData.image + } + } + }) + }) + } else { + cell.thumbnailImageView.image = nil + } + return cell + } + + // MARK: - Storyboard + + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + guard let nohanaImagePickerController = nohanaImagePickerController else { + return + } + + if isMomentRow(tableView.indexPathForSelectedRow!) { + let momentViewController = segue.destinationViewController as! MomentViewController + momentViewController.nohanaImagePickerController = nohanaImagePickerController + momentViewController.momentAlbumList = PhotoKitAlbumList( + assetCollectionTypes: [.Moment], + assetCollectionSubtypes: [.Any], + mediaType: nohanaImagePickerController.mediaType, + shouldShowEmptyAlbum: nohanaImagePickerController.shouldShowEmptyAlbum, + handler: { () -> Void in + dispatch_async(dispatch_get_main_queue(), { () -> Void in + momentViewController.isLoading = false + momentViewController.collectionView?.reloadData() + }) + }) + } else { + let assetListViewController = segue.destinationViewController as! AssetListViewController + assetListViewController.photoKitAssetList = photoKitAlbumList[exactRow(tableView.indexPathForSelectedRow!)] + assetListViewController.nohanaImagePickerController = nohanaImagePickerController + } + } + + // MARK: - IBAction + + @IBAction func didPushCancel(sender: AnyObject) { + guard let nohanaImagePickerController = nohanaImagePickerController else { + return + } + nohanaImagePickerController.delegate?.nohanaImagePickerDidCancel(nohanaImagePickerController) + } + + // MARK: - Private + + private func exactRow(indexPath: NSIndexPath) -> Int { + guard nohanaImagePickerController?.shouldShowMoment ?? false else { + return indexPath.row + } + return indexPath.row - 1 + } + + private func isMomentRow(indexPath: NSIndexPath) -> Bool { + return indexPath.row == 0 && (nohanaImagePickerController?.shouldShowMoment ?? false) + } + + // MARK: - EmptyIndicatable + + var emptyIndicator: UIView? + + func setUpEmptyIndicator() { + let frame = CGRect(origin: CGPoint.zero, size: Size.screenRectWithoutAppBar(self).size) + emptyIndicator = AlbumListEmptyIndicator( + message: NSLocalizedString("albumlist.empty.message", tableName: "NohanaImagePicker", bundle: nohanaImagePickerController!.assetBundle, comment: ""), + description: NSLocalizedString("albumlist.empty.description", tableName: "NohanaImagePicker", bundle: nohanaImagePickerController!.assetBundle, comment: ""), + frame: frame) + } + + func isEmpty() -> Bool { + if isProgressing() { + return false + } + return photoKitAlbumList.count == 0 + } + + // MARK: - ActivityIndicatable + + var activityIndicator: UIActivityIndicatorView? + var isLoading = true + + func setUpActivityIndicator() { + activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .Gray) + let screenRect = Size.screenRectWithoutAppBar(self) + activityIndicator?.center = CGPoint(x: screenRect.size.width / 2, y: screenRect.size.height / 2) + activityIndicator?.startAnimating() + } + + func isProgressing() -> Bool { + return isLoading + } +} + +@available(iOS 8.0, *) +extension UIViewController { + + // MARK: - Toolbar + + func setUpToolbarItems() { + let leftSpace = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace, target: nil, action: nil) + let rightSpace = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace, target: nil, action: nil) + + let infoButton = UIBarButtonItem(title: "", style: .Plain, target: nil, action: nil) + infoButton.enabled = false + infoButton.setTitleTextAttributes([NSFontAttributeName: UIFont.systemFontOfSize(14), NSForegroundColorAttributeName: UIColor.blackColor()], forState: .Normal) + self.toolbarItems = [leftSpace, infoButton, rightSpace] + } + + func setToolbarTitle(nohanaImagePickerController:NohanaImagePickerController?) { + guard toolbarItems?.count >= 2 else { + return + } + guard + let infoButton = toolbarItems?[1], + let nohanaImagePickerController = nohanaImagePickerController + else { + return + } + if nohanaImagePickerController.maximumNumberOfSelection == 0 { + let title = String(format: NSLocalizedString("toolbar.title.nolimit", tableName: "NohanaImagePicker", bundle: nohanaImagePickerController.assetBundle, comment: ""), + nohanaImagePickerController.pickedAssetList.count) + infoButton.title = title + } else { + let title = String(format: NSLocalizedString("toolbar.title.haslimit", tableName: "NohanaImagePicker", bundle: nohanaImagePickerController.assetBundle, comment: ""), + nohanaImagePickerController.pickedAssetList.count, + nohanaImagePickerController.maximumNumberOfSelection) + infoButton.title = title + } + } + + // MARK: - Notification + + func addPickPhotoKitAssetNotificationObservers() { + NSNotificationCenter.defaultCenter().addObserver(self, selector: "didPickPhotoKitAsset:", name: NotificationInfo.Asset.PhotoKit.didPick, object: nil) + NSNotificationCenter.defaultCenter().addObserver(self, selector: "didDropPhotoKitAsset:", name: NotificationInfo.Asset.PhotoKit.didDrop, object: nil) + } + + func didPickPhotoKitAsset(notification: NSNotification) { + guard let picker = notification.object as? NohanaImagePickerController else { + return + } + setToolbarTitle(picker) + } + + func didDropPhotoKitAsset(notification: NSNotification) { + guard let picker = notification.object as? NohanaImagePickerController else { + return + } + setToolbarTitle(picker) + } +} diff --git a/NohanaImagePicker/AnimatableNavigationController.swift b/NohanaImagePicker/AnimatableNavigationController.swift new file mode 100644 index 0000000..374e1db --- /dev/null +++ b/NohanaImagePicker/AnimatableNavigationController.swift @@ -0,0 +1,58 @@ +// +// AnimatableNavigationController.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/14. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit + +@available(iOS 8.0, *) +class AnimatableNavigationController: UINavigationController, UINavigationControllerDelegate, UIViewControllerTransitioningDelegate { + + let swipeInteractionController = SwipeInteractionController() + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.delegate = self + } + + func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { + + switch operation { + case .Push where fromVC is AssetListViewController: + guard let fromVC = fromVC as? AssetListViewController, + selectedIndex = fromVC.collectionView?.indexPathsForSelectedItems()?.first, + fromCell = fromVC.collectionView?.cellForItemAtIndexPath(selectedIndex) as? AssetCell + else { + return nil + } + return ExpandingAnimationController(fromCell: fromCell) + case .Pop where fromVC is AssetDetailListViewController: + guard let fromVC = fromVC as? AssetDetailListViewController, + fromCell = fromVC.collectionView?.cellForItemAtIndexPath(fromVC.currentIndexPath) as? AssetDetailCell + else { + return nil + } + return ContractingAnimationController(fromCell: fromCell) + default: + return nil + } + } + + func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) { + swipeInteractionController.attachToViewController(viewController) + } + + func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + if animationController is ExpandingAnimationController { + return nil + } + if animationController is ContractingAnimationController { + return nil + } + return swipeInteractionController + } + +} diff --git a/NohanaImagePicker/AssetCell.swift b/NohanaImagePicker/AssetCell.swift new file mode 100644 index 0000000..f6b3193 --- /dev/null +++ b/NohanaImagePicker/AssetCell.swift @@ -0,0 +1,55 @@ +// +// AssetCell.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/11. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit + +@available(iOS 8.0, *) +class AssetCell: UICollectionViewCell { + + @IBOutlet weak var imageView: UIImageView! + @IBOutlet weak var pickButton: UIButton! + @IBOutlet weak var overlayView: UIView! + + weak var nohanaImagePickerController: NohanaImagePickerController? + var asset: AssetType? + + override func willMoveToSuperview(newSuperview: UIView?) { + super.willMoveToSuperview(newSuperview) + + pickButton.setImage( + UIImage(named: ImageName.AssetCell.PickButton.SizeM.dropped, inBundle: nohanaImagePickerController?.assetBundle, compatibleWithTraitCollection: nil), + forState: .Normal) + pickButton.setImage( + UIImage(named: ImageName.AssetCell.PickButton.SizeM.picked, inBundle: nohanaImagePickerController?.assetBundle, compatibleWithTraitCollection: nil), + forState: [.Normal, .Selected]) + } + + @IBAction func didPushPickButton(sender: UIButton) { + guard let asset = asset, nohanaImagePickerController = nohanaImagePickerController else { + return + } + if pickButton.selected { + if nohanaImagePickerController.pickedAssetList.dropAsset(asset) { + pickButton.selected = false + } + } else { + if nohanaImagePickerController.pickedAssetList.pickAsset(asset) { + pickButton.selected = true + } + } + self.overlayView.hidden = !pickButton.selected + } + + func update(asset: AssetType, nohanaImagePickerController: NohanaImagePickerController?) { + self.asset = asset + self.nohanaImagePickerController = nohanaImagePickerController + self.pickButton.selected = nohanaImagePickerController?.pickedAssetList.isPicked(asset) ?? false + self.overlayView.hidden = !pickButton.selected + self.pickButton.hidden = !(nohanaImagePickerController?.canPickAsset(asset) ?? true) + } +} \ No newline at end of file diff --git a/NohanaImagePicker/AssetDetailCell.swift b/NohanaImagePicker/AssetDetailCell.swift new file mode 100644 index 0000000..229bca7 --- /dev/null +++ b/NohanaImagePicker/AssetDetailCell.swift @@ -0,0 +1,66 @@ +// +// AssetDetailCell.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/12. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit + +class AssetDetailCell: UICollectionViewCell, UIScrollViewDelegate { + + @IBOutlet weak var scrollView: UIScrollView! + @IBOutlet weak var imageView: UIImageView! + + @IBOutlet weak var imageViewHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var imageViewWidthConstraint: NSLayoutConstraint! + let doubleTapGestureRecognizer :UITapGestureRecognizer = UITapGestureRecognizer() + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + doubleTapGestureRecognizer.addTarget(self, action: "didDoubleTap:") + doubleTapGestureRecognizer.numberOfTapsRequired = 2 + } + + override func willMoveToSuperview(newSuperview: UIView?) { + super.willMoveToSuperview(newSuperview) + scrollView.removeGestureRecognizer(doubleTapGestureRecognizer) + scrollView.addGestureRecognizer(doubleTapGestureRecognizer) + } + + deinit { + scrollView.removeGestureRecognizer(doubleTapGestureRecognizer) + doubleTapGestureRecognizer.removeTarget(self, action: "didDoubleTap:") + } + + // MARK: - UIScrollViewDelegate + + func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? { + return imageView + } + + // MARK: - Zoom + + func didDoubleTap(sender: UITapGestureRecognizer) { + if scrollView.zoomScale < scrollView.maximumZoomScale { + let center = sender.locationInView(imageView) + scrollView.zoomToRect(zoomRect(center), animated: true) + } else { + let defaultScale: CGFloat = 1 + scrollView.setZoomScale(defaultScale, animated: true) + } + } + + func zoomRect(center: CGPoint) -> CGRect { + var zoomRect: CGRect = CGRect() + zoomRect.size.height = scrollView.frame.size.height / scrollView.maximumZoomScale + zoomRect.size.width = scrollView.frame.size.width / scrollView.maximumZoomScale + + zoomRect.origin.x = center.x - zoomRect.size.width / 2.0 + zoomRect.origin.y = center.y - zoomRect.size.height / 2.0 + + return zoomRect + } + +} \ No newline at end of file diff --git a/NohanaImagePicker/AssetDetailListViewController.swift b/NohanaImagePicker/AssetDetailListViewController.swift new file mode 100644 index 0000000..e138737 --- /dev/null +++ b/NohanaImagePicker/AssetDetailListViewController.swift @@ -0,0 +1,133 @@ +// +// AssetDetailListViewController.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/11. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit + +@available(iOS 8.0, *) +class AssetDetailListViewController: AssetListViewController { + + var selectedIndexPath:NSIndexPath! + var currentIndexPath: NSIndexPath = NSIndexPath() { + willSet { + if currentIndexPath != newValue { + didChangeAssetDetailPage(newValue) + } + } + } + @IBOutlet weak var pickButton: UIButton! + + override var cellSize: CGSize { + get { + return Size.screenRectWithoutAppBar(self).size + } + } + + override func viewDidLoad() { + super.viewDidLoad() + pickButton.setImage( + UIImage(named: ImageName.AssetCell.PickButton.SizeL.dropped, inBundle: nohanaImagePickerController?.assetBundle, compatibleWithTraitCollection: nil), + forState: .Normal) + pickButton.setImage( + UIImage(named: ImageName.AssetCell.PickButton.SizeL.picked, inBundle: nohanaImagePickerController?.assetBundle, compatibleWithTraitCollection: nil), + forState: [.Normal, .Selected]) + } + + override func viewWillAppear(animated: Bool) { + super.viewWillAppear(animated) + } + + override func updateTitle() { + self.title = "" + } + + func didChangeAssetDetailPage(indexPath:NSIndexPath) { + let asset = photoKitAssetList[indexPath.item] + pickButton.selected = nohanaImagePickerController?.pickedAssetList.isPicked(asset) ?? false + pickButton.hidden = !(nohanaImagePickerController?.canPickAsset(asset) ?? true) + guard let nohanaImagePickerController = nohanaImagePickerController else { + return + } + nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, assetDetailListViewController: self, didChangeAssetDetailPage: indexPath, photoKitAsset: asset.originalAsset) + } + + override func scrollCollectionViewToInitialPosition() { + guard isFirstAppearance else { + return + } + isFirstAppearance = false + guard photoKitAssetList.count > 0 else { + return + } + collectionView?.scrollToItemAtIndexPath(self.selectedIndexPath, atScrollPosition: UICollectionViewScrollPosition.CenteredHorizontally, animated: false) + } + + // MARK: - IBAction + + @IBAction func didPushPickButton(sender: UIButton) { + guard let nohanaImagePickerController = nohanaImagePickerController else { + return + } + let asset = photoKitAssetList[currentIndexPath.row] + if pickButton.selected { + if nohanaImagePickerController.pickedAssetList.dropAsset(asset) { + pickButton.selected = false + } + } else { + if nohanaImagePickerController.pickedAssetList.pickAsset(asset) { + pickButton.selected = true + } + } + } + + // MARK: - UICollectionViewDelegate + + override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCellWithReuseIdentifier("AssetDetailCell", forIndexPath: indexPath) as? AssetDetailCell, + nohanaImagePickerController = nohanaImagePickerController + else { + return UICollectionViewCell(frame: CGRectZero) + } + cell.scrollView.zoomScale = 1 + cell.tag = indexPath.item + + let imageSize = CGSize( + width: cellSize.width * UIScreen.mainScreen().scale, + height: cellSize.height * UIScreen.mainScreen().scale + ) + let asset = photoKitAssetList[indexPath.item] + asset.image(imageSize) { (imageData) -> Void in + dispatch_async(dispatch_get_main_queue(), { () -> Void in + if let imageData = imageData { + if cell.tag == indexPath.item { + cell.imageView.image = imageData.image + cell.imageViewHeightConstraint.constant = self.cellSize.height + cell.imageViewWidthConstraint.constant = self.cellSize.width + } + } + }) + } + return (nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, assetDetailListViewController: self, cell: cell, indexPath: indexPath, photoKitAsset: asset.originalAsset)) ?? cell + } + + // MARK: - UIScrollViewDelegate + + override func scrollViewDidScroll(scrollView: UIScrollView) { + guard let collectionView = collectionView else { + return + } + let row = Int((collectionView.contentOffset.x + cellSize.width * 0.5) / cellSize.width) + currentIndexPath = NSIndexPath(forRow: row, inSection: 0) + } + + // MARK: - UICollectionViewDelegateFlowLayout + + override func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { + return cellSize + } + +} \ No newline at end of file diff --git a/NohanaImagePicker/AssetListViewController.swift b/NohanaImagePicker/AssetListViewController.swift new file mode 100644 index 0000000..6bc9d07 --- /dev/null +++ b/NohanaImagePicker/AssetListViewController.swift @@ -0,0 +1,141 @@ +// +// AssetListViewController.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/11. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit +import Photos + +@available(iOS 8.0, *) +class AssetListViewController: UICollectionViewController { + + weak var nohanaImagePickerController: NohanaImagePickerController? + var photoKitAssetList: PhotoKitAssetList! + + override func viewDidLoad() { + super.viewDidLoad() + updateTitle() + setUpToolbarItems() + addPickPhotoKitAssetNotificationObservers() + self.view.backgroundColor = ColorConfig.backgroundColor + } + + var cellSize: CGSize { + get { + guard let nohanaImagePickerController = nohanaImagePickerController else { + return CGSize.zero + } + var numberOfColumns = nohanaImagePickerController.numberOfColumnsInLandscape + if UIInterfaceOrientationIsPortrait(UIApplication.sharedApplication().statusBarOrientation) { + numberOfColumns = nohanaImagePickerController.numberOfColumnsInPortrait + } + let cellMargin:CGFloat = 2 + let cellWidth = (view.frame.width - cellMargin * (CGFloat(numberOfColumns) - 1)) / CGFloat(numberOfColumns) + return CGSize(width: cellWidth, height: cellWidth) + } + } + + deinit { + NSNotificationCenter.defaultCenter().removeObserver(self) + } + + override func viewWillAppear(animated: Bool) { + super.viewWillAppear(animated) + setToolbarTitle(nohanaImagePickerController) + collectionView?.reloadData() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + scrollCollectionViewToInitialPosition() + } + + var isFirstAppearance = true + + func updateTitle() { + title = photoKitAssetList.title + } + + func scrollCollectionViewToInitialPosition() { + guard isFirstAppearance else { + return + } + guard photoKitAssetList.count > 0 else { + return + } + let index = NSIndexPath(forRow: self.photoKitAssetList.count - 1, inSection: 0) + collectionView?.scrollToItemAtIndexPath(index, atScrollPosition: .Bottom, animated: false) + + isFirstAppearance = false + } + + // MARK: - UICollectionViewDataSource + + override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return photoKitAssetList.count + } + + // MARK: - UICollectionViewDelegate + + override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { + guard let nohanaImagePickerController = nohanaImagePickerController else { + return + } + nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, didSelectPhotoKitAsset: photoKitAssetList[indexPath.item].originalAsset) + } + + override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCellWithReuseIdentifier("AssetCell", forIndexPath: indexPath) as? AssetCell, + nohanaImagePickerController = nohanaImagePickerController else { + return UICollectionViewCell(frame: CGRectZero) + } + cell.tag = indexPath.item + cell.update(photoKitAssetList[indexPath.row], nohanaImagePickerController: nohanaImagePickerController) + + let imageSize = CGSize( + width: cellSize.width * UIScreen.mainScreen().scale, + height: cellSize.height * UIScreen.mainScreen().scale + ) + let asset = photoKitAssetList[indexPath.item] + asset.image(imageSize) { (imageData) -> Void in + dispatch_async(dispatch_get_main_queue(), { () -> Void in + if let imageData = imageData { + if cell.tag == indexPath.item { + cell.imageView.image = imageData.image + } + } + }) + } + return (nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, assetListViewController: self, cell: cell, indexPath: indexPath, photoKitAsset: asset.originalAsset)) ?? cell + } + + // MARK: - UICollectionViewDelegateFlowLayout + + func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { + return cellSize + } + + // MARK: - Storyboard + + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + let assetListDetailViewController = segue.destinationViewController as! AssetDetailListViewController + assetListDetailViewController.photoKitAssetList = photoKitAssetList + assetListDetailViewController.nohanaImagePickerController = nohanaImagePickerController + assetListDetailViewController.selectedIndexPath = collectionView?.indexPathsForSelectedItems()?.first + } + + // MARK: - IBAction + @IBAction func didPushDone(sender: AnyObject) { + guard let nohanaImagePickerController = nohanaImagePickerController else { + return + } + + let pickedPhotoKitAssets = nohanaImagePickerController.pickedAssetList.map{ ($0 as! PhotoKitAsset).originalAsset } + nohanaImagePickerController.delegate?.nohanaImagePicker(nohanaImagePickerController, didFinishPickingPhotoKitAssets: pickedPhotoKitAssets ) + } +} + + diff --git a/NohanaImagePicker/ColorConfig.swift b/NohanaImagePicker/ColorConfig.swift new file mode 100644 index 0000000..eff2cbb --- /dev/null +++ b/NohanaImagePicker/ColorConfig.swift @@ -0,0 +1,16 @@ +// +// ColorConfig.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/03/23. +// Copyright © 2016年 nohana. All rights reserved. +// + +public struct ColorConfig { + public static var backgroundColor = UIColor.whiteColor() + public static var emptyIndicator = UIColor(red: 0x88/0xff, green: 0x88/0xff, blue: 0x88/0xff, alpha: 1) + + public struct AlbumList { + public static var momentCellSeparator = UIColor(red: 0xbb/0xff, green: 0xbb/0xff, blue: 0xbb/0xff, alpha: 1) + } +} \ No newline at end of file diff --git a/NohanaImagePicker/ContractingAnimationController.swift b/NohanaImagePicker/ContractingAnimationController.swift new file mode 100644 index 0000000..2d44dcf --- /dev/null +++ b/NohanaImagePicker/ContractingAnimationController.swift @@ -0,0 +1,90 @@ +// +// ContractingAnimationController.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/15. +// Copyright © 2016年 nohana. All rights reserved. +// + +import AVFoundation + +@available(iOS 8.0, *) +extension Size { + static func contractingAnimationToCellRect(toVC: AssetListViewController, toCell: AssetCell) -> CGRect { + let origin = CGPoint(x: toCell.frame.origin.x, y: toCell.frame.origin.y - toVC.collectionView!.contentOffset.y) + return CGRect(origin: origin, size: toCell.frame.size) + } + + static func contractingAnimationFromCellRect(fromVC: AssetDetailListViewController, fromCell: AssetDetailCell, contractingImageSize: CGSize) -> CGRect { + var rect = AVMakeRectWithAspectRatioInsideRect(contractingImageSize, fromCell.imageView.frame) + rect.origin.y += Size.appBarHeight(fromVC) + rect.origin.x -= fromCell.scrollView.contentOffset.x + rect.origin.y -= fromCell.scrollView.contentOffset.y + return rect + } +} + +@available(iOS 8.0, *) +class ContractingAnimationController: NSObject, UIViewControllerAnimatedTransitioning { + + var fromCell: AssetDetailCell + + init(fromCell: AssetDetailCell) { + self.fromCell = fromCell + } + + func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { + return 0.3 + } + + func animateTransition(transitionContext: UIViewControllerContextTransitioning) { + guard + let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as? AssetDetailListViewController, + toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as? AssetListViewController, + containerView = transitionContext.containerView(), + fromCellIndex = fromVC.collectionView?.indexPathForCell(fromCell) + else { + return + } + + var toCellTmp = toVC.collectionView?.cellForItemAtIndexPath(fromCellIndex) as? AssetCell + if toCellTmp == nil { + // if toCell is not shown in collection view, scroll collection view to toCell index path. + toVC.collectionView?.scrollToItemAtIndexPath(fromVC.currentIndexPath, atScrollPosition: .CenteredVertically, animated: false) + toVC.collectionView?.layoutIfNeeded() + toCellTmp = toVC.collectionView?.cellForItemAtIndexPath(fromCellIndex) as? AssetCell + } + + guard let toCell = toCellTmp else { + return + } + + let contractingImageView = UIImageView(image: fromCell.imageView.image) + contractingImageView.contentMode = toCell.imageView.contentMode + contractingImageView.clipsToBounds = true + contractingImageView.frame = Size.contractingAnimationFromCellRect(fromVC, fromCell: fromCell, contractingImageSize: contractingImageView.image!.size) + + containerView.addSubview(toVC.view) + containerView.addSubview(contractingImageView) + toVC.view.alpha = 0 + fromCell.alpha = 0 + toCell.alpha = 0 + + UIView.animateWithDuration( + transitionDuration(transitionContext), + delay: 0, + options: .CurveEaseInOut, + animations: { () -> Void in + toVC.view.alpha = 1 + contractingImageView.frame = Size.contractingAnimationToCellRect(toVC, toCell: toCell) + }) { (_) -> Void in + self.fromCell.alpha = 1 + toCell.alpha = 1 + contractingImageView.removeFromSuperview() + transitionContext.completeTransition(!transitionContext.transitionWasCancelled()) + } + + } + + +} \ No newline at end of file diff --git a/NohanaImagePicker/EmptyIndicatable.swift b/NohanaImagePicker/EmptyIndicatable.swift new file mode 100644 index 0000000..04086f3 --- /dev/null +++ b/NohanaImagePicker/EmptyIndicatable.swift @@ -0,0 +1,24 @@ +// +// EmptyIndicatable.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/03/23. +// Copyright © 2016年 nohana. All rights reserved. +// + +public protocol EmptyIndicatable { + func isEmpty() -> Bool + func updateVisibilityOfEmptyIndicator(emptyIndicator: UIView) +} + +public extension EmptyIndicatable where Self: UIViewController { + func updateVisibilityOfEmptyIndicator(emptyIndicator: UIView) { + if isEmpty(){ + if !view.subviews.contains(emptyIndicator) { + view.addSubview(emptyIndicator) + } + } else { + emptyIndicator.removeFromSuperview() + } + } +} \ No newline at end of file diff --git a/NohanaImagePicker/ExpandingAnimationController.swift b/NohanaImagePicker/ExpandingAnimationController.swift new file mode 100644 index 0000000..02e2f1c --- /dev/null +++ b/NohanaImagePicker/ExpandingAnimationController.swift @@ -0,0 +1,75 @@ +// +// ExpandingAnimationController.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/14. +// Copyright © 2016年 nohana. All rights reserved. +// + +import AVFoundation + +@available(iOS 8.0, *) +extension Size { + + static func expandingAnimationFromCellRect(fromVC: AssetListViewController, fromCell: AssetCell) -> CGRect { + let origin = CGPoint(x: fromCell.frame.origin.x, y: fromCell.frame.origin.y - fromVC.collectionView!.contentOffset.y) + return CGRect(origin: origin, size: fromCell.frame.size) + } + + static func expandingAnimationToCellRect(fromVC: UIViewController, toSize:CGSize) -> CGRect { + return AVMakeRectWithAspectRatioInsideRect(toSize, Size.screenRectWithoutAppBar(fromVC)) + } +} + +@available(iOS 8.0, *) +class ExpandingAnimationController: NSObject, UIViewControllerAnimatedTransitioning { + + var fromCell: AssetCell + + init(fromCell: AssetCell) { + self.fromCell = fromCell + } + + func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { + return 0.3 + } + + func animateTransition(transitionContext: UIViewControllerContextTransitioning) { + guard let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as? AssetListViewController, + toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as? AssetDetailListViewController, + containerView = transitionContext.containerView() + else { + return + } + + let expandingImageView = UIImageView(image: fromCell.imageView.image) + expandingImageView.contentMode = fromCell.imageView.contentMode + expandingImageView.clipsToBounds = true + expandingImageView.frame = Size.expandingAnimationFromCellRect(fromVC, fromCell: fromCell) + + containerView.addSubview(toVC.view) + containerView.addSubview(expandingImageView) + toVC.view.alpha = 0 + toVC.collectionView?.hidden = true + toVC.view.backgroundColor = UIColor.blackColor() + fromCell.alpha = 0 + + UIView.animateWithDuration( + transitionDuration(transitionContext), + delay: 0, + usingSpringWithDamping: 0.8, + initialSpringVelocity: 10, + options: .CurveEaseOut, + animations: { () -> Void in + toVC.view.alpha = 1 + expandingImageView.frame = Size.expandingAnimationToCellRect(fromVC, toSize: expandingImageView.image!.size) + }) { (_) -> Void in + self.fromCell.alpha = 1 + toVC.collectionView?.hidden = false + expandingImageView.removeFromSuperview() + transitionContext.completeTransition(!transitionContext.transitionWasCancelled()) + } + } + +} + diff --git a/NohanaImagePicker/ImageName.swift b/NohanaImagePicker/ImageName.swift new file mode 100644 index 0000000..f9ccc37 --- /dev/null +++ b/NohanaImagePicker/ImageName.swift @@ -0,0 +1,22 @@ +// +// ImageName.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/16. +// Copyright © 2016年 nohana. All rights reserved. +// + +struct ImageName { + struct AssetCell { + struct PickButton { + struct SizeM { + static let picked = "btn_selected_m" + static let dropped = "btn_select_m" + } + struct SizeL { + static let picked = "btn_selected_l" + static let dropped = "btn_select_l" + } + } + } +} \ No newline at end of file diff --git a/NohanaImagePicker/Images.xcassets/Contents.json b/NohanaImagePicker/Images.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/NohanaImagePicker/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/NohanaImagePicker/Images.xcassets/btn_select_l.imageset/Contents.json b/NohanaImagePicker/Images.xcassets/btn_select_l.imageset/Contents.json new file mode 100644 index 0000000..5e4b2c9 --- /dev/null +++ b/NohanaImagePicker/Images.xcassets/btn_select_l.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "btn_select_l@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "btn_select_l@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/NohanaImagePicker/Images.xcassets/btn_select_l.imageset/btn_select_l@2x.png b/NohanaImagePicker/Images.xcassets/btn_select_l.imageset/btn_select_l@2x.png new file mode 100644 index 0000000..3d1cb81 Binary files /dev/null and b/NohanaImagePicker/Images.xcassets/btn_select_l.imageset/btn_select_l@2x.png differ diff --git a/NohanaImagePicker/Images.xcassets/btn_select_l.imageset/btn_select_l@3x.png b/NohanaImagePicker/Images.xcassets/btn_select_l.imageset/btn_select_l@3x.png new file mode 100644 index 0000000..706f1f9 Binary files /dev/null and b/NohanaImagePicker/Images.xcassets/btn_select_l.imageset/btn_select_l@3x.png differ diff --git a/NohanaImagePicker/Images.xcassets/btn_select_m.imageset/Contents.json b/NohanaImagePicker/Images.xcassets/btn_select_m.imageset/Contents.json new file mode 100644 index 0000000..a4c6bec --- /dev/null +++ b/NohanaImagePicker/Images.xcassets/btn_select_m.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "btn_select_m@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "btn_select_m@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/NohanaImagePicker/Images.xcassets/btn_select_m.imageset/btn_select_m@2x.png b/NohanaImagePicker/Images.xcassets/btn_select_m.imageset/btn_select_m@2x.png new file mode 100644 index 0000000..0e3cac3 Binary files /dev/null and b/NohanaImagePicker/Images.xcassets/btn_select_m.imageset/btn_select_m@2x.png differ diff --git a/NohanaImagePicker/Images.xcassets/btn_select_m.imageset/btn_select_m@3x.png b/NohanaImagePicker/Images.xcassets/btn_select_m.imageset/btn_select_m@3x.png new file mode 100644 index 0000000..3b735af Binary files /dev/null and b/NohanaImagePicker/Images.xcassets/btn_select_m.imageset/btn_select_m@3x.png differ diff --git a/NohanaImagePicker/Images.xcassets/btn_selected_l.imageset/Contents.json b/NohanaImagePicker/Images.xcassets/btn_selected_l.imageset/Contents.json new file mode 100644 index 0000000..8d2c67f --- /dev/null +++ b/NohanaImagePicker/Images.xcassets/btn_selected_l.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "btn_selected_l@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "btn_selected_l@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/NohanaImagePicker/Images.xcassets/btn_selected_l.imageset/btn_selected_l@2x.png b/NohanaImagePicker/Images.xcassets/btn_selected_l.imageset/btn_selected_l@2x.png new file mode 100644 index 0000000..f5c27b9 Binary files /dev/null and b/NohanaImagePicker/Images.xcassets/btn_selected_l.imageset/btn_selected_l@2x.png differ diff --git a/NohanaImagePicker/Images.xcassets/btn_selected_l.imageset/btn_selected_l@3x.png b/NohanaImagePicker/Images.xcassets/btn_selected_l.imageset/btn_selected_l@3x.png new file mode 100644 index 0000000..3430330 Binary files /dev/null and b/NohanaImagePicker/Images.xcassets/btn_selected_l.imageset/btn_selected_l@3x.png differ diff --git a/NohanaImagePicker/Images.xcassets/btn_selected_m.imageset/Contents.json b/NohanaImagePicker/Images.xcassets/btn_selected_m.imageset/Contents.json new file mode 100644 index 0000000..307b92b --- /dev/null +++ b/NohanaImagePicker/Images.xcassets/btn_selected_m.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "btn_selected_m@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "btn_selected_m@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/NohanaImagePicker/Images.xcassets/btn_selected_m.imageset/btn_selected_m@2x.png b/NohanaImagePicker/Images.xcassets/btn_selected_m.imageset/btn_selected_m@2x.png new file mode 100644 index 0000000..d3a9259 Binary files /dev/null and b/NohanaImagePicker/Images.xcassets/btn_selected_m.imageset/btn_selected_m@2x.png differ diff --git a/NohanaImagePicker/Images.xcassets/btn_selected_m.imageset/btn_selected_m@3x.png b/NohanaImagePicker/Images.xcassets/btn_selected_m.imageset/btn_selected_m@3x.png new file mode 100644 index 0000000..a469dcf Binary files /dev/null and b/NohanaImagePicker/Images.xcassets/btn_selected_m.imageset/btn_selected_m@3x.png differ diff --git a/NohanaImagePicker/Images.xcassets/icon_photosize_alert_s.imageset/Contents.json b/NohanaImagePicker/Images.xcassets/icon_photosize_alert_s.imageset/Contents.json new file mode 100644 index 0000000..6f14b6e --- /dev/null +++ b/NohanaImagePicker/Images.xcassets/icon_photosize_alert_s.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "icon_photosize_alert_s@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "icon_photosize_alert_s@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/NohanaImagePicker/Images.xcassets/icon_photosize_alert_s.imageset/icon_photosize_alert_s@2x.png b/NohanaImagePicker/Images.xcassets/icon_photosize_alert_s.imageset/icon_photosize_alert_s@2x.png new file mode 100644 index 0000000..34f67ea Binary files /dev/null and b/NohanaImagePicker/Images.xcassets/icon_photosize_alert_s.imageset/icon_photosize_alert_s@2x.png differ diff --git a/NohanaImagePicker/Images.xcassets/icon_photosize_alert_s.imageset/icon_photosize_alert_s@3x.png b/NohanaImagePicker/Images.xcassets/icon_photosize_alert_s.imageset/icon_photosize_alert_s@3x.png new file mode 100644 index 0000000..80f0203 Binary files /dev/null and b/NohanaImagePicker/Images.xcassets/icon_photosize_alert_s.imageset/icon_photosize_alert_s@3x.png differ diff --git a/NohanaImagePicker/Images.xcassets/icon_photosize_low_s.imageset/Contents.json b/NohanaImagePicker/Images.xcassets/icon_photosize_low_s.imageset/Contents.json new file mode 100644 index 0000000..e884163 --- /dev/null +++ b/NohanaImagePicker/Images.xcassets/icon_photosize_low_s.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "icon_photosize_low_s@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "icon_photosize_low_s@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/NohanaImagePicker/Images.xcassets/icon_photosize_low_s.imageset/icon_photosize_low_s@2x.png b/NohanaImagePicker/Images.xcassets/icon_photosize_low_s.imageset/icon_photosize_low_s@2x.png new file mode 100644 index 0000000..e4fee63 Binary files /dev/null and b/NohanaImagePicker/Images.xcassets/icon_photosize_low_s.imageset/icon_photosize_low_s@2x.png differ diff --git a/NohanaImagePicker/Images.xcassets/icon_photosize_low_s.imageset/icon_photosize_low_s@3x.png b/NohanaImagePicker/Images.xcassets/icon_photosize_low_s.imageset/icon_photosize_low_s@3x.png new file mode 100644 index 0000000..68218a2 Binary files /dev/null and b/NohanaImagePicker/Images.xcassets/icon_photosize_low_s.imageset/icon_photosize_low_s@3x.png differ diff --git a/NohanaImagePicker/Info.plist b/NohanaImagePicker/Info.plist new file mode 100644 index 0000000..d3de8ee --- /dev/null +++ b/NohanaImagePicker/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/NohanaImagePicker/ItemListType.swift b/NohanaImagePicker/ItemListType.swift new file mode 100644 index 0000000..d39b2e0 --- /dev/null +++ b/NohanaImagePicker/ItemListType.swift @@ -0,0 +1,24 @@ +// +// ItemListType.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/14. +// Copyright © 2016年 nohana. All rights reserved. +// + +public protocol ItemListType: CollectionType { + typealias Item + var title:String { get } + func update(handler:(() -> Void)?) + subscript (index: Int) -> Item { get } +} + +public protocol AssetType { + var identifier:Int { get } + func image(targetSize:CGSize, handler: (ImageData?) -> Void) +} + +public struct ImageData { + public var image: UIImage + public var info: Dictionary? +} \ No newline at end of file diff --git a/NohanaImagePicker/MomentCell.swift b/NohanaImagePicker/MomentCell.swift new file mode 100644 index 0000000..2014664 --- /dev/null +++ b/NohanaImagePicker/MomentCell.swift @@ -0,0 +1,20 @@ +// +// MomentCell.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/03/23. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit + +class MomentCell: AlbumCell { + + override func drawRect(rect: CGRect) { + super.drawRect(rect) + let lineWidth: CGFloat = 1 / UIScreen.mainScreen().scale + ColorConfig.AlbumList.momentCellSeparator.setFill() + UIRectFill(CGRect(x: 16, y: frame.size.height - lineWidth, width: frame.size.width, height:lineWidth)) + } + +} diff --git a/NohanaImagePicker/MomentSectionHeaderView.swift b/NohanaImagePicker/MomentSectionHeaderView.swift new file mode 100644 index 0000000..0f2e22d --- /dev/null +++ b/NohanaImagePicker/MomentSectionHeaderView.swift @@ -0,0 +1,15 @@ +// +// MomentSectionHeaderView.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/03/09. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit + +class MomentSectionHeaderView: UICollectionReusableView { + + @IBOutlet weak var dateLabel: UILabel! + @IBOutlet weak var locationLabel: UILabel! +} diff --git a/NohanaImagePicker/MomentViewController.swift b/NohanaImagePicker/MomentViewController.swift new file mode 100644 index 0000000..92f0c43 --- /dev/null +++ b/NohanaImagePicker/MomentViewController.swift @@ -0,0 +1,143 @@ +// +// MomentViewController.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/03/08. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit +import Photos + +@available(iOS 8.0, *) +class MomentViewController: AssetListViewController, ActivityIndicatable { + + var momentAlbumList: PhotoKitAlbumList! + + override func viewDidLoad() { + super.viewDidLoad() + setUpActivityIndicator() + } + + override func updateTitle() { + title = NSLocalizedString("albumlist.moment.title", tableName: "NohanaImagePicker", bundle: nohanaImagePickerController!.assetBundle, comment: "") + } + + override func scrollCollectionViewToInitialPosition() { + guard isFirstAppearance else { + return + } + guard let collectionView = collectionView else { + return + } + let lastSection = momentAlbumList.count - 1 + guard lastSection >= 0 else { + return + } + + let index = NSIndexPath(forItem: momentAlbumList[lastSection].count - 1, inSection: lastSection) + collectionView.scrollToItemAtIndexPath(index, atScrollPosition: .Bottom, animated: false) + isFirstAppearance = false + + } + + // MARK: - UICollectionViewDataSource + + override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { + if let activityIndicator = activityIndicator { + updateVisibilityOfActivityIndicator(activityIndicator) + } + + return momentAlbumList.count + } + + override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return momentAlbumList[section].count + } + + // MARK: - UICollectionViewDelegate + + override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCellWithReuseIdentifier("AssetCell", forIndexPath: indexPath) as? AssetCell, + nohanaImagePickerController = nohanaImagePickerController else { + return UICollectionViewCell(frame: CGRectZero) + } + + let asset = momentAlbumList[indexPath.section][indexPath.row] + cell.tag = indexPath.item + cell.update(asset, nohanaImagePickerController: nohanaImagePickerController) + + let imageSize = CGSize( + width: cellSize.width * UIScreen.mainScreen().scale, + height: cellSize.height * UIScreen.mainScreen().scale + ) + asset.image(imageSize) { (imageData) -> Void in + dispatch_async(dispatch_get_main_queue(), { () -> Void in + if let imageData = imageData { + if cell.tag == indexPath.item { + cell.imageView.image = imageData.image + } + } + }) + } + return (nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, assetListViewController: self, cell: cell, indexPath: indexPath, photoKitAsset: asset.originalAsset)) ?? cell + } + + override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { + switch kind { + case UICollectionElementKindSectionHeader: + let album = momentAlbumList[indexPath.section] + guard let header = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "MomentHeader", forIndexPath: indexPath) as? MomentSectionHeaderView else { + fatalError("failed to create MomentHeader") + } + header.locationLabel.text = album.title + if let date = album.date { + let formatter = NSDateFormatter() + formatter.dateStyle = .LongStyle + formatter.timeStyle = NSDateFormatterStyle.NoStyle + header.dateLabel.text = formatter.stringFromDate(date) + } else { + header.dateLabel.text = "" + } + return header + default: + fatalError("failed to create MomentHeader") + } + } + + // MARK: - ActivityIndicatable + + var activityIndicator: UIActivityIndicatorView? + var isLoading = true + + func setUpActivityIndicator() { + activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .Gray) + let screenRect = Size.screenRectWithoutAppBar(self) + activityIndicator?.center = CGPoint(x: screenRect.size.width / 2, y: screenRect.size.height / 2) + activityIndicator?.startAnimating() + } + + func isProgressing() -> Bool { + return isLoading + } + + // MARK: - Storyboard + + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + guard let selectedIndexPath = collectionView?.indexPathsForSelectedItems()?.first else { + return + } + + let assetListDetailViewController = segue.destinationViewController as! AssetDetailListViewController + assetListDetailViewController.photoKitAssetList = momentAlbumList[selectedIndexPath.section] + assetListDetailViewController.nohanaImagePickerController = nohanaImagePickerController + assetListDetailViewController.selectedIndexPath = NSIndexPath(forItem: selectedIndexPath.item, inSection: 0) + } + + // MARK: - IBAction + + @IBAction override func didPushDone(sender: AnyObject) { + super.didPushDone(sender) + } + +} \ No newline at end of file diff --git a/NohanaImagePicker/NohanaImagePicker.h b/NohanaImagePicker/NohanaImagePicker.h new file mode 100644 index 0000000..8c1a226 --- /dev/null +++ b/NohanaImagePicker/NohanaImagePicker.h @@ -0,0 +1,18 @@ +// +// NohanaImagePicker.h +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/08. +// Copyright © 2016年 nohana. All rights reserved. +// + +#import + +//! Project version number for NohanaImagePicker. +FOUNDATION_EXPORT double NohanaImagePickerVersionNumber; + +//! Project version string for NohanaImagePicker. +FOUNDATION_EXPORT const unsigned char NohanaImagePickerVersionString[]; + +// In this header, you should import all the public headers of your framework +// using statements like #import diff --git a/NohanaImagePicker/NohanaImagePicker.storyboard b/NohanaImagePicker/NohanaImagePicker.storyboard new file mode 100644 index 0000000..af92788 --- /dev/null +++ b/NohanaImagePicker/NohanaImagePicker.storyboard @@ -0,0 +1,442 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NohanaImagePicker/NohanaImagePickerController.swift b/NohanaImagePicker/NohanaImagePickerController.swift new file mode 100644 index 0000000..7354152 --- /dev/null +++ b/NohanaImagePicker/NohanaImagePickerController.swift @@ -0,0 +1,121 @@ +// +// NohanaImagePickerController.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/09. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit +import Photos + +public enum MediaType: Int { + case Any = 0, Photo, Video +} + +@available(iOS 8.0, *) +@objc public protocol NohanaImagePickerControllerDelegate { + func nohanaImagePickerDidCancel(picker: NohanaImagePickerController) + func nohanaImagePicker(picker: NohanaImagePickerController, didFinishPickingPhotoKitAssets pickedAssts :[PHAsset]) + optional func nohanaImagePicker(picker: NohanaImagePickerController, willPickPhotoKitAsset asset: PHAsset, pickedAssetsCount: Int) -> Bool + optional func nohanaImagePicker(picker: NohanaImagePickerController, didPickPhotoKitAsset asset: PHAsset, pickedAssetsCount: Int) + optional func nohanaImagePicker(picker: NohanaImagePickerController, willDropPhotoKitAsset asset: PHAsset, pickedAssetsCount: Int) -> Bool + optional func nohanaImagePicker(picker: NohanaImagePickerController, didDropPhotoKitAsset asset: PHAsset, pickedAssetsCount: Int) + optional func nohanaImagePicker(picker: NohanaImagePickerController, didSelectPhotoKitAsset asset: PHAsset) + optional func nohanaImagePicker(picker: NohanaImagePickerController, didSelectPhotoKitAssetList assetList: PHAssetCollection) + optional func nohanaImagePicker(picker: NohanaImagePickerController, assetListViewController: UICollectionViewController, cell: UICollectionViewCell, indexPath: NSIndexPath, photoKitAsset: PHAsset) -> UICollectionViewCell + optional func nohanaImagePicker(picker: NohanaImagePickerController, assetDetailListViewController: UICollectionViewController, cell: UICollectionViewCell, indexPath: NSIndexPath, photoKitAsset: PHAsset) -> UICollectionViewCell + optional func nohanaImagePicker(picker: NohanaImagePickerController, assetDetailListViewController: UICollectionViewController, didChangeAssetDetailPage indexPath: NSIndexPath, photoKitAsset: PHAsset) + +} + +@available(iOS 8.0, *) +public class NohanaImagePickerController: UIViewController { + + public var maximumNumberOfSelection: Int = 21 // set 0 to set no limit + public var numberOfColumnsInPortrait: Int = 4 + public var numberOfColumnsInLandscape: Int = 7 + public weak var delegate: NohanaImagePickerControllerDelegate? + public var shouldShowMoment: Bool = true + public var shouldShowEmptyAlbum: Bool = false + public var toolbarHidden: Bool = false + public var canPickAsset = { (asset:AssetType) -> Bool in + return true + } + lazy var assetBundle:NSBundle = NSBundle(forClass: self.dynamicType) + let pickedAssetList: PickedAssetList + let mediaType: MediaType + private let assetCollectionSubtypes: [PHAssetCollectionSubtype] + + public init() { + assetCollectionSubtypes = [ + .AlbumRegular, + .AlbumSyncedEvent, + .AlbumSyncedFaces, + .AlbumSyncedAlbum, + .AlbumImported, + .AlbumMyPhotoStream, + .AlbumCloudShared, + .SmartAlbumGeneric, + .SmartAlbumFavorites, + .SmartAlbumRecentlyAdded, + .SmartAlbumUserLibrary + ] + mediaType = .Photo + pickedAssetList = PickedAssetList() + super.init(nibName: nil, bundle: nil) + self.pickedAssetList.nohanaImagePickerController = self + } + + public init(assetCollectionSubtypes: [PHAssetCollectionSubtype], mediaType: MediaType) { + self.assetCollectionSubtypes = assetCollectionSubtypes + self.mediaType = mediaType + pickedAssetList = PickedAssetList() + super.init(nibName: nil, bundle: nil) + self.pickedAssetList.nohanaImagePickerController = self + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func viewDidLoad() { + super.viewDidLoad() + + // show albumListViewController + let storyboard = UIStoryboard(name: "NohanaImagePicker", bundle: assetBundle) + guard let navigationController = storyboard.instantiateInitialViewController() as? UINavigationController else { + fatalError("navigationController init failed.") + } + addChildViewController(navigationController) + view.addSubview(navigationController.view) + navigationController.didMoveToParentViewController(self) + + // setup albumListViewController + guard let albumListViewController = navigationController.topViewController as? AlbumListViewController else { + fatalError("albumListViewController is not topViewController.") + } + albumListViewController.photoKitAlbumList = + PhotoKitAlbumList( + assetCollectionTypes: [.SmartAlbum, .Album], + assetCollectionSubtypes: assetCollectionSubtypes, + mediaType: mediaType, + shouldShowEmptyAlbum: shouldShowEmptyAlbum, + handler: { + dispatch_async(dispatch_get_main_queue(), { () -> Void in + albumListViewController.isLoading = false + albumListViewController.tableView.reloadData() + }) + }) + albumListViewController.nohanaImagePickerController = self + } + + public func pickAsset(asset: AssetType) { + pickedAssetList.pickAsset(asset) + } + + public func dropAsset(asset: AssetType) { + pickedAssetList.dropAsset(asset) + } +} + diff --git a/NohanaImagePicker/NotificationInfo.swift b/NohanaImagePicker/NotificationInfo.swift new file mode 100644 index 0000000..e7771b6 --- /dev/null +++ b/NohanaImagePicker/NotificationInfo.swift @@ -0,0 +1,20 @@ +// +// NotificationInfo.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/19. +// Copyright © 2016年 nohana. All rights reserved. +// + +struct NotificationInfo { + struct Asset { + struct PhotoKit { + static let didPick = "jp.co.nohana.NotificationName.Asset.PhotoKit.didPick" + static let didPickUserInfoKeyAsset = "asset" + static let didPickUserInfoKeyPickedAssetsCount = "pickedAssetsCount" + static let didDrop = "jp.co.nohana.NotificationName.Asset.PhotoKit.didDrop" + static let didDropUserInfoKeyAsset = "asset" + static let didDropUserInfoKeyPickedAssetsCount = "pickedAssetsCount" + } + } +} \ No newline at end of file diff --git a/NohanaImagePicker/PhotoKitAlbumList.swift b/NohanaImagePicker/PhotoKitAlbumList.swift new file mode 100644 index 0000000..054dac7 --- /dev/null +++ b/NohanaImagePicker/PhotoKitAlbumList.swift @@ -0,0 +1,98 @@ +// +// PhotoKitAlbumList.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/10. +// Copyright © 2016年 nohana. All rights reserved. +// + +import Photos + +@available(iOS 8.0, *) +public class PhotoKitAlbumList: ItemListType { + + private var albumList:[Item] = [] + private let assetCollectionTypes: [PHAssetCollectionType] + private let assetCollectionSubtypes: [PHAssetCollectionSubtype] + private let mediaType: MediaType + private var shouldShowEmptyAlbum: Bool + + // MARK: - init + + init(assetCollectionTypes: [PHAssetCollectionType], assetCollectionSubtypes: [PHAssetCollectionSubtype], mediaType: MediaType, shouldShowEmptyAlbum: Bool, handler:(() -> Void)?) { + self.assetCollectionTypes = assetCollectionTypes + self.assetCollectionSubtypes = assetCollectionSubtypes + self.mediaType = mediaType + self.shouldShowEmptyAlbum = shouldShowEmptyAlbum + update { () -> Void in + if let handler = handler { + handler() + } + } + } + + // MARK: - ItemListType + + public typealias Item = PhotoKitAssetList + + public var title:String { + get { + return "PhotoKit" + } + } + + public func update(handler:(() -> Void)?) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in + var albumListFetchResult: [PHFetchResult] = [] + for type in self.assetCollectionTypes { + albumListFetchResult = albumListFetchResult + [PHAssetCollection.fetchAssetCollectionsWithType(type, subtype: .Any, options: nil)] + } + + self.albumList = [] + var tmpAlbumList:[Item] = [] + let isAssetCollectionSubtypeAny = self.assetCollectionSubtypes.contains(.Any) + for fetchResult in albumListFetchResult { + fetchResult.enumerateObjectsUsingBlock { (album, index, stop) -> Void in + guard let album = album as? PHAssetCollection else { + return + } + if self.assetCollectionSubtypes.contains(album.assetCollectionSubtype) || isAssetCollectionSubtypeAny { + if self.shouldShowEmptyAlbum || PHAsset.fetchAssetsInAssetCollection(album, options: PhotoKitAssetList.fetchOptions(self.mediaType)).count != 0 { + tmpAlbumList.append(PhotoKitAssetList(album: album, mediaType: self.mediaType)) + } + } + } + } + if self.assetCollectionTypes == [.Moment] { + self.albumList = tmpAlbumList.sort{ $0.date?.timeIntervalSince1970 < $1.date?.timeIntervalSince1970 } + } else { + self.albumList = tmpAlbumList + } + + if let handler = handler { + handler() + } + } + } + + public subscript (index: Int) -> Item { + get { + return albumList[index] as Item + } + } + + // MARK: - CollectionType + + public var startIndex: Int { + get { + return albumList.startIndex + } + } + + public var endIndex: Int { + get { + return albumList.endIndex + } + } + +} diff --git a/NohanaImagePicker/PhotoKitAsset.swift b/NohanaImagePicker/PhotoKitAsset.swift new file mode 100644 index 0000000..2d9a4c2 --- /dev/null +++ b/NohanaImagePicker/PhotoKitAsset.swift @@ -0,0 +1,50 @@ +// +// PhotoKitAsset.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/11. +// Copyright © 2016年 nohana. All rights reserved. +// + +import Photos + +@available(iOS 8.0, *) +public class PhotoKitAsset :AssetType { + + let asset: PHAsset + + public init(asset: PHAsset) { + self.asset = asset + } + + public var originalAsset: PHAsset { + get { + return asset as PHAsset + } + } + + // MARK: - AssetType + + public var identifier:Int { + get { + return asset.localIdentifier.hash + } + } + + public func image(targetSize:CGSize, handler: (ImageData?) -> Void) { + let option = PHImageRequestOptions() + option.networkAccessAllowed = true + + PHImageManager.defaultManager().requestImageForAsset( + self.asset, + targetSize: targetSize, + contentMode: .AspectFit, + options: option ) { (image, info) -> Void in + guard let image = image else { + handler(nil) + return + } + handler(ImageData(image: image, info: info)) + } + } +} diff --git a/NohanaImagePicker/PhotoKitAssetList.swift b/NohanaImagePicker/PhotoKitAssetList.swift new file mode 100644 index 0000000..8d9471c --- /dev/null +++ b/NohanaImagePicker/PhotoKitAssetList.swift @@ -0,0 +1,79 @@ +// +// PhotoKitAssetList.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/11. +// Copyright © 2016年 nohana. All rights reserved. +// +import Photos + +@available(iOS 8.0, *) +public class PhotoKitAssetList :ItemListType { + + private let mediaType: MediaType + public let assetList: PHAssetCollection + private var fetchResult: PHFetchResult! + + init(album: PHAssetCollection, mediaType: MediaType) { + self.assetList = album + self.mediaType = mediaType + update() + } + + // MARK: - ItemListType + + public typealias Item = PhotoKitAsset + + public var title: String { + get{ + return assetList.localizedTitle ?? "" + } + } + + public var date: NSDate? { + get { + return assetList.startDate + } + } + + class func fetchOptions(mediaType: MediaType) -> PHFetchOptions { + let options = PHFetchOptions() + switch mediaType { + case .Photo: + options.predicate = NSPredicate(format: "mediaType == %ld", PHAssetMediaType.Image.rawValue) + default: + fatalError("not supported .Video and .Any yet") + } + return options + } + + public func update(handler: (() -> Void)? = nil) { + fetchResult = PHAsset.fetchAssetsInAssetCollection(assetList, options: PhotoKitAssetList.fetchOptions(mediaType)) + if let handler = handler { + handler() + } + } + + public subscript (index: Int) -> Item { + get { + guard let asset = fetchResult[index] as? PHAsset else { + fatalError("invalid index") + } + return Item(asset: asset) + } + } + + // MARK: - CollectionType + + public var startIndex: Int { + get { + return 0 + } + } + + public var endIndex: Int { + get { + return fetchResult.count + } + } +} diff --git a/NohanaImagePicker/PickedAssetList.swift b/NohanaImagePicker/PickedAssetList.swift new file mode 100644 index 0000000..524bce7 --- /dev/null +++ b/NohanaImagePicker/PickedAssetList.swift @@ -0,0 +1,124 @@ +// +// PickedAssetList.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/17. +// Copyright © 2016年 nohana. All rights reserved. +// + +import Foundation + +@available(iOS 8.0, *) +class PickedAssetList: ItemListType { + + var assetlist: Array = [] + weak var nohanaImagePickerController: NohanaImagePickerController? + + // MARK: - ItemListType + + typealias Item = AssetType + + var title: String { + get { + return "Selected Assets" + } + } + + func update(handler:(() -> Void)?) { + fatalError("not supported") + } + + + subscript (index: Int) -> Item { + get { + return assetlist[index] + } + } + + // MARK: - CollectionType + + var startIndex: Int { + get { + return 0 + } + } + + var endIndex: Int { + get { + return assetlist.count + } + } + + // MARK: - Manage assetlist + + func pickAsset(asset: AssetType) -> Bool { + guard !isPicked(asset) else { + return false + } + guard let nohanaImagePickerController = nohanaImagePickerController else { + return false + } + let assetsCountBeforePicking = self.count + if asset is PhotoKitAsset { + if let canPick = nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, willPickPhotoKitAsset: (asset as! PhotoKitAsset).originalAsset, pickedAssetsCount: assetsCountBeforePicking) + where !canPick { + return false + } + } + guard nohanaImagePickerController.maximumNumberOfSelection == 0 || assetsCountBeforePicking < nohanaImagePickerController.maximumNumberOfSelection else { + return false + } + assetlist.append(asset) + let assetsCountAfterPicking = self.count + if asset is PhotoKitAsset { + let originalAsset = (asset as! PhotoKitAsset).originalAsset + nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, didPickPhotoKitAsset: originalAsset, pickedAssetsCount: assetsCountAfterPicking) + NSNotificationCenter.defaultCenter().postNotification( + NSNotification( + name: NotificationInfo.Asset.PhotoKit.didPick, + object: nohanaImagePickerController, + userInfo: [ + NotificationInfo.Asset.PhotoKit.didPickUserInfoKeyAsset : originalAsset, + NotificationInfo.Asset.PhotoKit.didPickUserInfoKeyPickedAssetsCount : assetsCountAfterPicking + ] + ) + ) + } + return true + + } + + func dropAsset(asset: AssetType) -> Bool { + guard let nohanaImagePickerController = nohanaImagePickerController else { + return false + } + let assetsCountBeforeDropping = self.count + if asset is PhotoKitAsset { + if let canDrop = nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, willDropPhotoKitAsset: (asset as! PhotoKitAsset).originalAsset, pickedAssetsCount: assetsCountBeforeDropping) where !canDrop { + return false + } + } + assetlist = assetlist.filter{ $0.identifier != asset.identifier } + let assetsCountAfterDropping = self.count + if asset is PhotoKitAsset { + let originalAsset = (asset as! PhotoKitAsset).originalAsset + nohanaImagePickerController.delegate?.nohanaImagePicker?(nohanaImagePickerController, didDropPhotoKitAsset: originalAsset, pickedAssetsCount: assetsCountAfterDropping) + NSNotificationCenter.defaultCenter().postNotification( + NSNotification( + name: NotificationInfo.Asset.PhotoKit.didDrop, + object: nohanaImagePickerController, + userInfo: [ + NotificationInfo.Asset.PhotoKit.didDropUserInfoKeyAsset : originalAsset, + NotificationInfo.Asset.PhotoKit.didDropUserInfoKeyPickedAssetsCount : assetsCountAfterDropping + ] + ) + ) + } + return true + } + + func isPicked(asset: AssetType) -> Bool { + return assetlist.contains{ $0.identifier == asset.identifier } + } + +} \ No newline at end of file diff --git a/NohanaImagePicker/Size.swift b/NohanaImagePicker/Size.swift new file mode 100644 index 0000000..ed56025 --- /dev/null +++ b/NohanaImagePicker/Size.swift @@ -0,0 +1,40 @@ +// +// Size.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/14. +// Copyright © 2016年 nohana. All rights reserved. +// + +struct Size { + + static let statusBarHeight = UIApplication.sharedApplication().statusBarFrame.size.height + + static func navigationBarHeight(viewController: UIViewController) -> CGFloat { + return viewController.navigationController?.navigationBar.frame.size.height ?? CGFloat(44) + } + + static func appBarHeight(viewController: UIViewController) -> CGFloat { + return statusBarHeight + navigationBarHeight(viewController) + } + + static func toolbarHeight(viewController: UIViewController) -> CGFloat { + guard let navigationController = viewController.navigationController else { + return 0 + } + guard !navigationController.toolbarHidden else { + return 0 + } + return navigationController.toolbar.frame.size.height + } + + static func screenRectWithoutAppBar(viewController: UIViewController) -> CGRect { + let appBarHeight = Size.appBarHeight(viewController) + let toolbarHeight = Size.toolbarHeight(viewController) + return CGRect( + x: 0, + y: appBarHeight, + width: UIScreen.mainScreen().bounds.width, + height: UIScreen.mainScreen().bounds.height - appBarHeight - toolbarHeight) + } +} diff --git a/NohanaImagePicker/SwipeInteractionController.swift b/NohanaImagePicker/SwipeInteractionController.swift new file mode 100644 index 0000000..737f7c3 --- /dev/null +++ b/NohanaImagePicker/SwipeInteractionController.swift @@ -0,0 +1,25 @@ +// +// SwipeInteractionController.swift +// NohanaImagePicker +// +// Created by kazushi.hara on 2016/02/21. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit + +@available(iOS 8.0, *) +class SwipeInteractionController: UIPercentDrivenInteractiveTransition { + + var viewController: UIViewController? + + func attachToViewController(viewController: UIViewController) { + guard viewController.navigationController?.viewControllers.count > 1 else { + return + } + let target = viewController.navigationController?.valueForKey("_cachedInteractionController") + let gesture = UIScreenEdgePanGestureRecognizer(target: target, action: "handleNavigationTransition:") + gesture.edges = .Left + viewController.view.addGestureRecognizer(gesture) + } +} \ No newline at end of file diff --git a/NohanaImagePicker/en.lproj/NohanaImagePicker.strings b/NohanaImagePicker/en.lproj/NohanaImagePicker.strings new file mode 100644 index 0000000..547da45 --- /dev/null +++ b/NohanaImagePicker/en.lproj/NohanaImagePicker.strings @@ -0,0 +1,14 @@ +/* + NohanaImagePicker.strings + NohanaImagePicker + + Created by kazushi.hara on 2016/02/15. + Copyright © 2016年 nohana. All rights reserved. +*/ + +"albumlist.title" = "Photos"; +"albumlist.empty.message" = "No Photos"; +"albumlist.empty.description" = "Take some photos with Camera app."; +"albumlist.moment.title" = "Moment"; +"toolbar.title.nolimit" = "Selected Items: %ld"; +"toolbar.title.haslimit" = "Selected Items: %ld / %ld"; diff --git a/NohanaImagePicker/ja.lproj/NohanaImagePicker.strings b/NohanaImagePicker/ja.lproj/NohanaImagePicker.strings new file mode 100644 index 0000000..389c186 --- /dev/null +++ b/NohanaImagePicker/ja.lproj/NohanaImagePicker.strings @@ -0,0 +1,14 @@ +/* + NohanaImagePicker.strings + NohanaImagePicker + + Created by kazushi.hara on 2016/02/15. + Copyright © 2016年 nohana. All rights reserved. +*/ + +"albumlist.title" = "アルバム"; +"albumlist.empty.message" = "アルバムに\n写真がありません"; +"albumlist.empty.description" = "カメラで写真を撮影しましょう"; +"albumlist.moment.title" = "日付から選択"; +"toolbar.title.nolimit" = "%ld枚選択中"; +"toolbar.title.haslimit" = "%ld枚選択中(最大%ld枚)"; diff --git a/NohanaImagePickerSample/AppDelegate.swift b/NohanaImagePickerSample/AppDelegate.swift new file mode 100644 index 0000000..263e01f --- /dev/null +++ b/NohanaImagePickerSample/AppDelegate.swift @@ -0,0 +1,46 @@ +// +// AppDelegate.swift +// NohanaImagePickerSample +// +// Created by kazushi.hara on 2016/02/08. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(application: UIApplication) { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + +} + diff --git a/NohanaImagePickerSample/Assets.xcassets/AppIcon.appiconset/Contents.json b/NohanaImagePickerSample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..eeea76c --- /dev/null +++ b/NohanaImagePickerSample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,73 @@ +{ + "images" : [ + { + "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" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/NohanaImagePickerSample/Base.lproj/LaunchScreen.storyboard b/NohanaImagePickerSample/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..2e721e1 --- /dev/null +++ b/NohanaImagePickerSample/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NohanaImagePickerSample/Base.lproj/Main.storyboard b/NohanaImagePickerSample/Base.lproj/Main.storyboard new file mode 100644 index 0000000..bd6e4ab --- /dev/null +++ b/NohanaImagePickerSample/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NohanaImagePickerSample/Info.plist b/NohanaImagePickerSample/Info.plist new file mode 100644 index 0000000..40c6215 --- /dev/null +++ b/NohanaImagePickerSample/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/NohanaImagePickerSample/ViewController.swift b/NohanaImagePickerSample/ViewController.swift new file mode 100644 index 0000000..2cd7d3e --- /dev/null +++ b/NohanaImagePickerSample/ViewController.swift @@ -0,0 +1,85 @@ +// +// ViewController.swift +// NohanaImagePickerSample +// +// Created by kazushi.hara on 2016/02/08. +// Copyright © 2016年 nohana. All rights reserved. +// + +import UIKit +import Photos +import NohanaImagePicker + +class ViewController: UIViewController, NohanaImagePickerControllerDelegate { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + } + + override func viewDidAppear(animated: Bool) { + let picker = NohanaImagePickerController() + picker.delegate = self + picker.maximumNumberOfSelection = 4 + picker.canPickAsset = { (asset:AssetType) -> Bool in + return asset.identifier % 10 != 0 + } + presentViewController(picker, animated: true, completion: nil) + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + // MARK: - NohanaImagePickerControllerDelegate + + func nohanaImagePickerDidCancel(picker: NohanaImagePickerController) { + picker.dismissViewControllerAnimated(true, completion: nil) + } + + func nohanaImagePicker(picker: NohanaImagePickerController, didFinishPickingPhotoKitAssets pickedAssts :[PHAsset]) { + picker.dismissViewControllerAnimated(true, completion: nil) + } + + func willPickPhotoKitAsset(asset: PHAsset, pickedAssetsCount: Int) -> Bool { + print("func:\(__FUNCTION__), asset: \(asset), pickedAssetsCount: \(pickedAssetsCount)") + return true + } + + func didPickPhotoKitAsset(asset: PHAsset, pickedAssetsCount: Int) { + print("func:\(__FUNCTION__), asset: \(asset), pickedAssetsCount: \(pickedAssetsCount)") + } + + func willDropPhotoKitAsset(asset: PHAsset, pickedAssetsCount: Int) -> Bool { + print("func:\(__FUNCTION__), asset: \(asset), pickedAssetsCount: \(pickedAssetsCount)") + return true + } + + func didDropPhotoKitAsset(asset: PHAsset, pickedAssetsCount: Int) { + print("func:\(__FUNCTION__), asset: \(asset), pickedAssetsCount: \(pickedAssetsCount)") + } + + func didSelectPhotoKitAsset(asset: PHAsset) { + print("func:\(__FUNCTION__), assetList: \(asset)") + } + + func didSelectPhotoKitAssetList(assetList: PHAssetCollection) { + print("func:\(__FUNCTION__), assetList: \(assetList)") + } + + func assetListView(collectionView: UICollectionView, cell: UICollectionViewCell, indexPath: NSIndexPath, photoKitAsset: PHAsset) -> UICollectionViewCell { + print("func:\(__FUNCTION__), cell:\(cell), indexPath: \(indexPath), photoKitAsset: \(photoKitAsset)") + return cell + } + + func assetDetailListView(collectionView: UICollectionView, cell: UICollectionViewCell, indexPath: NSIndexPath, photoKitAsset: PHAsset) -> UICollectionViewCell { + print("func:\(__FUNCTION__), cell:\(cell), indexPath: \(indexPath), photoKitAsset: \(photoKitAsset)") + return cell + } + + func didChangeAssetDetailPage(indexPath: NSIndexPath, photoKitAsset: PHAsset) { + print("func:\(__FUNCTION__), indexPath: \(indexPath), photoKitAsset: \(photoKitAsset)") + } +} + diff --git a/NohanaImagePickerSampleTests/Info.plist b/NohanaImagePickerSampleTests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/NohanaImagePickerSampleTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/NohanaImagePickerSampleTests/NohanaImagePickerSampleTests.swift b/NohanaImagePickerSampleTests/NohanaImagePickerSampleTests.swift new file mode 100644 index 0000000..ee42e3a --- /dev/null +++ b/NohanaImagePickerSampleTests/NohanaImagePickerSampleTests.swift @@ -0,0 +1,36 @@ +// +// NohanaImagePickerSampleTests.swift +// NohanaImagePickerSampleTests +// +// Created by kazushi.hara on 2016/02/08. +// Copyright © 2016年 nohana. All rights reserved. +// + +import XCTest +@testable import NohanaImagePickerSample + +class NohanaImagePickerSampleTests: 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.measureBlock { + // Put the code you want to measure the time of here. + } + } + +}