Commit b46db5a2 by Daniel Dahan

development: removed Capture, Events, and Photos

parent 6fbfcb75
......@@ -14,12 +14,7 @@
9617B07E1DFCA8CF00410F8F /* Card.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB75D1CB40DC500C806FE /* Card.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B07F1DFCA8CF00410F8F /* ImageCard.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7621CB40DC500C806FE /* ImageCard.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0801DFCA8CF00410F8F /* PresenterCard.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9631A7C01D95E3AC00CFB109 /* PresenterCard.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0811DFCA8CF00410F8F /* Capture.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B0D1DBE6AF600DA84DB /* Capture.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0821DFCA8CF00410F8F /* CapturePreview.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B0F1DBE6AF600DA84DB /* CapturePreview.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0831DFCA8CF00410F8F /* CaptureController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B0E1DBE6AF600DA84DB /* CaptureController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0861DFCA8CF00410F8F /* HeightPreset.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9626CB9A1DAD3D1D003E2611 /* HeightPreset.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0881DFCA8CF00410F8F /* PhotoLibrary.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B161DBE6B1800DA84DB /* PhotoLibrary.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B0891DFCA8CF00410F8F /* PhotoLibraryController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B171DBE6B1800DA84DB /* PhotoLibraryController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B08A1DFCA8CF00410F8F /* DisplayStyle.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9626CA961DAB53A8003E2611 /* DisplayStyle.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B08B1DFCA8CF00410F8F /* Screen.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961E6BE11DDA2AF3004E6C93 /* Screen.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B08C1DFCA8CF00410F8F /* SearchBar.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7951CB40DC500C806FE /* SearchBar.swift */; settings = {ATTRIBUTES = (Public, ); }; };
......@@ -37,10 +32,6 @@
96328B9E1E05C24E009A4C90 /* TableView.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96328B961E05C0BB009A4C90 /* TableView.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96328B9F1E05C24E009A4C90 /* TableViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96328B981E05C0CE009A4C90 /* TableViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96334EF61C8B84660083986B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 96334EF51C8B84660083986B /* Assets.xcassets */; };
9639526C1EC3882F004BA9DE /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9639526A1EC3882F004BA9DE /* Events.swift */; };
9639526D1EC3882F004BA9DE /* EventsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9639526B1EC3882F004BA9DE /* EventsController.swift */; };
964335B71EC9432400FA9954 /* Events.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9639526A1EC3882F004BA9DE /* Events.swift */; settings = {ATTRIBUTES = (Public, ); }; };
964335B81EC9432400FA9954 /* EventsController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9639526B1EC3882F004BA9DE /* EventsController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
964335BA1EC9432400FA9954 /* TabsController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9606CFAB1E957AC3006B4E74 /* TabsController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9656895F1F002F16001C656D /* CardCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9656895E1F002F16001C656D /* CardCollectionViewCell.swift */; };
965689611F002F4C001C656D /* CardCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965689601F002F4C001C656D /* CardCollectionViewController.swift */; };
......@@ -85,9 +76,6 @@
965E80FD1DD4D59500D61E4B /* Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB79F1CB40DC500C806FE /* Toolbar.swift */; };
965E80FE1DD4D59500D61E4B /* ToolbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7A01CB40DC500C806FE /* ToolbarController.swift */; };
965E80FF1DD4D5C800D61E4B /* BottomNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7581CB40DC500C806FE /* BottomNavigationController.swift */; };
965E81001DD4D5C800D61E4B /* Capture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96717B0D1DBE6AF600DA84DB /* Capture.swift */; };
965E81011DD4D5C800D61E4B /* CapturePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96717B0F1DBE6AF600DA84DB /* CapturePreview.swift */; };
965E81021DD4D5C800D61E4B /* CaptureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96717B0E1DBE6AF600DA84DB /* CaptureController.swift */; };
965E81031DD4D5C800D61E4B /* CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7711CB40DC500C806FE /* CollectionView.swift */; };
965E81041DD4D5C800D61E4B /* CollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7721CB40DC500C806FE /* CollectionViewCell.swift */; };
965E81071DD4D5C800D61E4B /* CollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7751CB40DC500C806FE /* CollectionViewLayout.swift */; };
......@@ -100,8 +88,6 @@
965E81111DD4D5C800D61E4B /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7911CB40DC500C806FE /* NavigationController.swift */; };
965E81121DD4D5C800D61E4B /* NavigationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7921CB40DC500C806FE /* NavigationItem.swift */; };
965E81131DD4D5C800D61E4B /* NavigationDrawerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7971CB40DC500C806FE /* NavigationDrawerController.swift */; };
965E81141DD4D5C800D61E4B /* PhotoLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96717B161DBE6B1800DA84DB /* PhotoLibrary.swift */; };
965E81151DD4D5C800D61E4B /* PhotoLibraryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96717B171DBE6B1800DA84DB /* PhotoLibraryController.swift */; };
965E81161DD4D5C800D61E4B /* DisplayStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9626CA961DAB53A8003E2611 /* DisplayStyle.swift */; };
965E81171DD4D5C800D61E4B /* RootController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7991CB40DC500C806FE /* RootController.swift */; };
965E81181DD4D5C800D61E4B /* Snackbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 963FBEFC1D669510008F8512 /* Snackbar.swift */; };
......@@ -220,8 +206,6 @@
96328B981E05C0CE009A4C90 /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = "<group>"; };
96334EF51C8B84660083986B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
963832361B88DFD80015F710 /* Material.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Material.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9639526A1EC3882F004BA9DE /* Events.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = "<group>"; };
9639526B1EC3882F004BA9DE /* EventsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventsController.swift; sourceTree = "<group>"; };
963FBEFC1D669510008F8512 /* Snackbar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Snackbar.swift; sourceTree = "<group>"; };
965532281E47E388005C2792 /* SpringAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpringAnimation.swift; sourceTree = "<group>"; };
9656895E1F002F16001C656D /* CardCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardCollectionViewCell.swift; sourceTree = "<group>"; };
......@@ -229,11 +213,6 @@
9658F2161CD6FA4700B902C1 /* IconButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IconButton.swift; sourceTree = "<group>"; };
966A7F191EEC5D5000A2DAAC /* Motion.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Motion.xcodeproj; path = Frameworks/Motion/Motion.xcodeproj; sourceTree = "<group>"; };
966ECF291CF4C20100BB0BDF /* CollectionReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionReusableView.swift; sourceTree = "<group>"; };
96717B0D1DBE6AF600DA84DB /* Capture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Capture.swift; sourceTree = "<group>"; };
96717B0E1DBE6AF600DA84DB /* CaptureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaptureController.swift; sourceTree = "<group>"; };
96717B0F1DBE6AF600DA84DB /* CapturePreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CapturePreview.swift; sourceTree = "<group>"; };
96717B161DBE6B1800DA84DB /* PhotoLibrary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoLibrary.swift; sourceTree = "<group>"; };
96717B171DBE6B1800DA84DB /* PhotoLibraryController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoLibraryController.swift; sourceTree = "<group>"; };
967A48181D0F425A00B8CEB7 /* StatusBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarController.swift; sourceTree = "<group>"; };
968C99461D377849000074FF /* Offset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Offset.swift; sourceTree = "<group>"; };
96A183621E0C6CE200083C30 /* FABMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FABMenu.swift; sourceTree = "<group>"; };
......@@ -411,15 +390,6 @@
name = Products;
sourceTree = "<group>";
};
963952691EC3882F004BA9DE /* Events */ = {
isa = PBXGroup;
children = (
9639526A1EC3882F004BA9DE /* Events.swift */,
9639526B1EC3882F004BA9DE /* EventsController.swift */,
);
path = Events;
sourceTree = "<group>";
};
963FBEFB1D6694E8008F8512 /* Snackbar */ = {
isa = PBXGroup;
children = (
......@@ -508,25 +478,6 @@
name = Table;
sourceTree = "<group>";
};
96717B0C1DBE6AF600DA84DB /* Capture */ = {
isa = PBXGroup;
children = (
96717B0D1DBE6AF600DA84DB /* Capture.swift */,
96717B0F1DBE6AF600DA84DB /* CapturePreview.swift */,
96717B0E1DBE6AF600DA84DB /* CaptureController.swift */,
);
path = Capture;
sourceTree = "<group>";
};
96717B151DBE6B1800DA84DB /* Photos */ = {
isa = PBXGroup;
children = (
96717B161DBE6B1800DA84DB /* PhotoLibrary.swift */,
96717B171DBE6B1800DA84DB /* PhotoLibraryController.swift */,
);
path = Photos;
sourceTree = "<group>";
};
967A48171D0F424B00B8CEB7 /* StatusBar */ = {
isa = PBXGroup;
children = (
......@@ -552,13 +503,11 @@
962DDD081D6FBBD0001C307C /* BottomTabBar */,
96BCB8031CB40F4B00C806FE /* Button */,
96BCB8021CB40F3B00C806FE /* Card */,
96717B0C1DBE6AF600DA84DB /* Capture */,
96BCB8051CB40F9C00C806FE /* Collection */,
96BCB8001CB40F0300C806FE /* Color */,
96328B9A1E05C135009A4C90 /* Data */,
96BCB80B1CB410CC00C806FE /* Device */,
96230AB61D6A51FD00AF47DC /* Divider */,
963952691EC3882F004BA9DE /* Events */,
96BCB80A1CB410A100C806FE /* Extension */,
963FBF021D6696D0008F8512 /* FABMenu */,
96BCB8071CB4101C00C806FE /* Font */,
......@@ -570,7 +519,6 @@
96BCB8011CB40F1700C806FE /* Navigation */,
961E6BEF1DDA4B04004E6C93 /* NavigationDrawer */,
96DE566D1EF1B6E3006DA70E /* Pan */,
96717B151DBE6B1800DA84DB /* Photos */,
9626CA951DAB5370003E2611 /* Root */,
961E6BE01DDA2ADD004E6C93 /* Screen */,
963FBF031D6696EF008F8512 /* SearchBar */,
......@@ -850,12 +798,7 @@
9617B07E1DFCA8CF00410F8F /* Card.swift in Headers */,
9617B07F1DFCA8CF00410F8F /* ImageCard.swift in Headers */,
9617B0801DFCA8CF00410F8F /* PresenterCard.swift in Headers */,
9617B0811DFCA8CF00410F8F /* Capture.swift in Headers */,
9617B0821DFCA8CF00410F8F /* CapturePreview.swift in Headers */,
9617B0831DFCA8CF00410F8F /* CaptureController.swift in Headers */,
9617B0861DFCA8CF00410F8F /* HeightPreset.swift in Headers */,
9617B0881DFCA8CF00410F8F /* PhotoLibrary.swift in Headers */,
9617B0891DFCA8CF00410F8F /* PhotoLibraryController.swift in Headers */,
9617B08A1DFCA8CF00410F8F /* DisplayStyle.swift in Headers */,
9617B08B1DFCA8CF00410F8F /* Screen.swift in Headers */,
9617B08C1DFCA8CF00410F8F /* SearchBar.swift in Headers */,
......@@ -870,8 +813,6 @@
961409B11E43D15C00E7BA99 /* FABMenu.swift in Headers */,
961409B21E43D15C00E7BA99 /* FABMenuController.swift in Headers */,
96BFC16F1E63C10A0075DE1F /* SpringAnimation.swift in Headers */,
964335B71EC9432400FA9954 /* Events.swift in Headers */,
964335B81EC9432400FA9954 /* EventsController.swift in Headers */,
964335BA1EC9432400FA9954 /* TabsController.swift in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
......@@ -975,9 +916,6 @@
961E6BE21DDA2AF3004E6C93 /* Screen.swift in Sources */,
965E81261DD4D7C800D61E4B /* CharacterAttribute.swift in Sources */,
965E80FF1DD4D5C800D61E4B /* BottomNavigationController.swift in Sources */,
965E81001DD4D5C800D61E4B /* Capture.swift in Sources */,
965E81011DD4D5C800D61E4B /* CapturePreview.swift in Sources */,
965E81021DD4D5C800D61E4B /* CaptureController.swift in Sources */,
965E81031DD4D5C800D61E4B /* CollectionView.swift in Sources */,
965E81041DD4D5C800D61E4B /* CollectionViewCell.swift in Sources */,
965E81071DD4D5C800D61E4B /* CollectionViewLayout.swift in Sources */,
......@@ -991,8 +929,6 @@
965E81111DD4D5C800D61E4B /* NavigationController.swift in Sources */,
965E81121DD4D5C800D61E4B /* NavigationItem.swift in Sources */,
965E81131DD4D5C800D61E4B /* NavigationDrawerController.swift in Sources */,
965E81141DD4D5C800D61E4B /* PhotoLibrary.swift in Sources */,
965E81151DD4D5C800D61E4B /* PhotoLibraryController.swift in Sources */,
9656895F1F002F16001C656D /* CardCollectionViewCell.swift in Sources */,
965E81161DD4D5C800D61E4B /* DisplayStyle.swift in Sources */,
965E81171DD4D5C800D61E4B /* RootController.swift in Sources */,
......@@ -1001,11 +937,9 @@
965E81191DD4D5C800D61E4B /* SnackbarController.swift in Sources */,
965E811A1DD4D5C800D61E4B /* StatusBarController.swift in Sources */,
965E811B1DD4D5C800D61E4B /* Switch.swift in Sources */,
9639526C1EC3882F004BA9DE /* Events.swift in Sources */,
965E811C1DD4D5C800D61E4B /* TabBar.swift in Sources */,
965E811D1DD4D5C800D61E4B /* TableViewCell.swift in Sources */,
965E811E1DD4D5C800D61E4B /* TextField.swift in Sources */,
9639526D1EC3882F004BA9DE /* EventsController.swift in Sources */,
965E811F1DD4D5C800D61E4B /* ErrorTextField.swift in Sources */,
965E81211DD4D5C800D61E4B /* TextStorage.swift in Sources */,
965E81221DD4D5C800D61E4B /* TextView.swift in Sources */,
......
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
import AVFoundation
@objc(CaptureMode)
public enum CaptureMode: Int {
case photo
case video
}
fileprivate var CaptureAdjustingExposureContext: UInt8 = 0
@objc(CapturePreset)
public enum CapturePreset: Int {
case presetPhoto
case presetHigh
case presetMedium
case presetLow
case preset352x288
case preset640x480
case preset1280x720
case preset1920x1080
case preset3840x2160
case presetiFrame960x540
case presetiFrame1280x720
case presetInputPriority
}
/**
Converts a given CaptureSessionPreset to a String value.
- Parameter preset: A CaptureSessionPreset to convert.
*/
public func CapturePresetToString(preset: CapturePreset) -> String {
switch preset {
case .presetPhoto:
return AVCaptureSessionPresetPhoto
case .presetHigh:
return AVCaptureSessionPresetHigh
case .presetMedium:
return AVCaptureSessionPresetMedium
case .presetLow:
return AVCaptureSessionPresetLow
case .preset352x288:
return AVCaptureSessionPreset352x288
case .preset640x480:
return AVCaptureSessionPreset640x480
case .preset1280x720:
return AVCaptureSessionPreset1280x720
case .preset1920x1080:
return AVCaptureSessionPreset1920x1080
case .preset3840x2160:
if #available(iOS 9.0, *) {
return AVCaptureSessionPreset3840x2160
} else {
return AVCaptureSessionPresetHigh
}
case .presetiFrame960x540:
return AVCaptureSessionPresetiFrame960x540
case .presetiFrame1280x720:
return AVCaptureSessionPresetiFrame1280x720
case .presetInputPriority:
return AVCaptureSessionPresetInputPriority
}
}
@objc(CaptureDelegate)
public protocol CaptureDelegate {
/**
A delegation method that is executed when the captureSesstion failes with an error.
- Parameter capture: A reference to the calling capture.
- Parameter error: A Error corresponding to the error.
*/
@objc
optional func capture(capture: Capture, failedWith error: Error)
/**
A delegation method that is executed when the record timer has started.
- Parameter capture: A reference to the calling capture.
- Parameter didStartRecord timer: A Timer.
*/
@objc
optional func capture(capture: Capture, didStartRecord timer: Timer)
/**
A delegation method that is executed when the record timer was updated.
- Parameter capture: A reference to the calling capture.
- Parameter didUpdateRecord timer: A Timer.
- Parameter hours: An integer representing hours.
- Parameter minutes: An integer representing minutes.
- Parameter seconds: An integer representing seconds.
*/
@objc
optional func capture(capture: Capture, didUpdateRecord timer: Timer, hours: Int, minutes: Int, seconds: Int)
/**
A delegation method that is executed when the record timer has stopped.
- Parameter capture: A reference to the calling capture.
- Parameter didStopRecord timer: A Timer.
- Parameter hours: An integer representing hours.
- Parameter minutes: An integer representing minutes.
- Parameter seconds: An integer representing seconds.
*/
@objc
optional func capture(capture: Capture, didStopRecord time: Timer, hours: Int, minutes: Int, seconds: Int)
/**
A delegation method that is executed when the user tapped to adjust the focus.
- Parameter capture: A reference to the calling capture.
- Parameter didTapToFocusAt point: CGPoint that the user tapped at.
*/
@objc
optional func capture(capture: Capture, didTapToFocusAt point: CGPoint)
/**
A delegation method that is executed when the user tapped to adjust the exposure.
- Parameter capture: A reference to the calling capture.
- Parameter didTapToExposeAt point: CGPoint that the user tapped at.
*/
@objc
optional func capture(capture: Capture, didTapToExposeAt point: CGPoint)
/**
A delegation method that is executed when the user tapped to reset.
- Parameter capture: A reference to the calling capture.
- Parameter didTapToResetAt point: CGPoint that the user tapped at.
*/
@objc
optional func capture(capture: Capture, didTapToResetAt point: CGPoint)
/**
A delegation method that is executed when the user pressed the change mode button.
- Parameter capture: A reference to the calling capture.
- Parameter didPressChangeMode button: A reference to the UIButton that the user pressed.
*/
@objc
optional func capture(capture: Capture, didPressChangeMode button: UIButton)
/**
A delegation method that is executed when the user pressed the change camera button.
- Parameter capture: A reference to the calling capture.
- Parameter didPressChangeCamera button: A reference to the UIButton that the user pressed.
*/
@objc
optional func capture(capture: Capture, didPressChangeCamera button: UIButton)
/**
A delegation method that is executed when the user pressed capture button.
- Parameter capture: A reference to the calling capture.
- Parameter didPressCapture button: A reference to the UIButton that the user pressed.
*/
@objc
optional func capture(capture: Capture, didPressCapture button: UIButton)
/**
A delegation method that is fired when the user pressed the flash button.
- Parameter capture: A reference to the calling capture.
- Parameter didPressFlash button: A reference to the UIButton that the user pressed.
*/
@objc
optional func capture(capture: Capture, didPressFlash button: UIButton)
/**
A delegation method that is executed before the camera has been changed to another mode.
- Parameter capture: A reference to the calling capture.
- Parameter mode: A CaptureMode.
*/
@objc
optional func capture(capture: Capture, willChange mode: CaptureMode)
/**
A delegation method that is executed after the camera has been changed to another mode.
- Parameter capture: A reference to the calling capture.
- Parameter mode: A CaptureMode.
*/
@objc
optional func capture(capture: Capture, didChange mode: CaptureMode)
/**
A delegation method that is executed before the camera has been changed to another.
- Parameter capture: A reference to the calling capture.
- Parameter willChangeCamera devicePosition: An AVCaptureDevicePosition that the camera will change to.
*/
@objc
optional func capture(capture: Capture, willChangeCamera devicePosition: AVCaptureDevicePosition)
/**
A delegation method that is executed when the camera has been changed to another.
- Parameter capture: A reference to the calling capture.
- Parameter didChangeCamera devicePosition: An AVCaptureDevicePosition that the camera has changed to.
*/
@objc
optional func capture(capture: Capture, didChangeCamera devicePosition: AVCaptureDevicePosition)
/**
A delegation method that is executed when the device orientation changes.
- Parameter capture: A reference to the calling capture.
- Paremeter didChange videoOrientation: An AVCaptureVideoOrientation value.
*/
@objc
optional func capture(capture: Capture, didChangeFrom previousVideoOrientation: AVCaptureVideoOrientation, to videoOrientation: AVCaptureVideoOrientation)
/**
A delegation method that is executed when an image has been captured asynchronously.
- Parameter capture: A reference to the calling capture.
- Parameter asynchronouslyStill image: An image that has been captured.
*/
@objc
optional func capture(capture: Capture, asynchronouslyStill image: UIImage)
/**
A delegation method that is executed when capturing an image asynchronously has failed.
- Parameter capture: A reference to the calling capture.
- Parameter asynchronouslyStillImageFailedWith error: A Error corresponding to the error.
*/
@objc
optional func capture(capture: Capture, asynchronouslyStillImageFailedWith error: Error)
/**
A delegation method that is executed when creating a movie file has failed.
- Parameter capture: A reference to the calling capture.
- Parameter createMovieFileFailedWith error: A Error corresponding to the error.
*/
@objc
optional func capture(capture: Capture, createMovieFileFailedWith error: Error)
/**
A delegation method that is executed when a session started recording and writing
to a file.
- Parameter capture: A reference to the calling capture.
- Parameter captureOutput: An AVCaptureFileOutput.
- Parameter didStartRecordingToOutputFileAt fileURL: A file URL.
- Parameter fromConnections: An array of Anys.
*/
@objc
optional func capture(capture: Capture, captureOutput: AVCaptureFileOutput, didStartRecordingToOutputFileAt fileURL: NSURL, fromConnections connections: [Any])
/**
A delegation method that is executed when a session finished recording and writing
to a file.
- Parameter capture: A reference to the calling capture.
- Parameter captureOutput: An AVCaptureFileOutput.
- Parameter didFinishRecordingToOutputFileAt outputFileURL: A file URL.
- Parameter fromConnections: An array of Anys.
- Parameter error: A Error corresponding to an error.
*/
@objc
optional func capture(capture: Capture, captureOutput: AVCaptureFileOutput, didFinishRecordingToOutputFileAt outputFileURL: NSURL, fromConnections connections: [Any], error: Error!)
}
open class Capture: View {
/// A reference to the capture mode.
open var mode = CaptureMode.photo
/// Delegation handler.
open weak var delegate: CaptureDelegate?
/// A reference to the CapturePreview view.
open let preview = CapturePreview()
/// A Timer reference for when recording is enabled.
open fileprivate(set) var timer: Timer?
/// A tap gesture reference for focus events.
fileprivate var tapToFocusGesture: UITapGestureRecognizer?
/// A tap gesture reference for exposure events.
fileprivate var tapToExposeGesture: UITapGestureRecognizer?
/// A tap gesture reference for reset events.
fileprivate var tapToResetGesture: UITapGestureRecognizer?
/// A reference to the session DispatchQueue.
fileprivate var sessionQueue: DispatchQueue!
/// A reference to the active video input.
fileprivate var activeVideoInput: AVCaptureDeviceInput?
/// A reference to the active audio input.
fileprivate var activeAudioInput: AVCaptureDeviceInput?
/// A reference to the image output.
fileprivate var imageOutput: AVCaptureStillImageOutput!
/// A reference to the movie output.
fileprivate var movieOutput: AVCaptureMovieFileOutput!
/// A reference to the movie output URL.
fileprivate var movieOutputURL: URL?
/// A reference to the AVCaptureSession.
fileprivate var session: AVCaptureSession!
/// A boolean indicating if the session is running.
open fileprivate(set) var isRunning = false
/// A boolean indicating if the session is recording.
open fileprivate(set) var isRecording = false
/// A reference to the recorded time duration.
open var recordedDuration: CMTime {
return movieOutput.recordedDuration
}
/// An optional reference to the active camera if one exists.
open var activeCamera: AVCaptureDevice? {
return activeVideoInput?.device
}
/// An optional reference to the inactive camera if one exists.
open var inactiveCamera: AVCaptureDevice? {
var device: AVCaptureDevice?
if 1 < cameraCount {
if activeCamera?.position == .back {
device = camera(at: .front)
} else {
device = camera(at: .back)
}
}
return device
}
/// Available number of cameras.
open var cameraCount: Int {
return AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo).count
}
/// A boolean indicating whether the camera can change to another.
open var canChangeCamera: Bool {
return 1 < cameraCount
}
/// A booealn indicating whether the camrea supports focus.
open var isFocusPointOfInterestSupported: Bool {
return nil == activeCamera ? false : activeCamera!.isFocusPointOfInterestSupported
}
/// A booealn indicating whether the camrea supports exposure.
open var isExposurePointOfInterestSupported: Bool {
return nil == activeCamera ? false : activeCamera!.isExposurePointOfInterestSupported
}
/// A boolean indicating if the active camera has flash.
open var isFlashAvailable: Bool {
return nil == activeCamera ? false : activeCamera!.hasFlash
}
/// A boolean indicating if the active camera has a torch.
open var isTorchAvailable: Bool {
return nil == activeCamera ? false : activeCamera!.hasTorch
}
/// A reference to the active camera position if the active camera exists.
open var devicePosition: AVCaptureDevicePosition? {
return activeCamera?.position
}
/// A reference to the focusMode.
open var focusMode: AVCaptureFocusMode {
get {
return activeCamera!.focusMode
}
set(value) {
var error: NSError?
if isFocusModeSupported(focusMode: focusMode) {
do {
let device = activeCamera!
try device.lockForConfiguration()
device.focusMode = value
device.unlockForConfiguration()
} catch let e as NSError {
error = e
}
} else {
var userInfo: Dictionary<String, Any> = Dictionary<String, Any>()
userInfo[NSLocalizedDescriptionKey] = "[Material Error: Unsupported focusMode.]"
userInfo[NSLocalizedFailureReasonErrorKey] = "[Material Error: Unsupported focusMode.]"
error = NSError(domain: "com.cosmicmind.material.capture", code: 0001, userInfo: userInfo)
userInfo[NSUnderlyingErrorKey] = error
}
if let e = error {
delegate?.capture?(capture: self, failedWith: e)
}
}
}
/// A reference to the flashMode.
open var flashMode: AVCaptureFlashMode {
get {
return activeCamera!.flashMode
}
set(value) {
var error: NSError?
if isFlashModeSupported(flashMode: flashMode) {
do {
let device = activeCamera!
try device.lockForConfiguration()
device.flashMode = value
device.unlockForConfiguration()
} catch let e as NSError {
error = e
}
} else {
var userInfo: Dictionary<String, Any> = Dictionary<String, Any>()
userInfo[NSLocalizedDescriptionKey] = "[Material Error: Unsupported flashMode.]"
userInfo[NSLocalizedFailureReasonErrorKey] = "[Material Error: Unsupported flashMode.]"
error = NSError(domain: "com.cosmicmind.material.capture", code: 0002, userInfo: userInfo)
userInfo[NSUnderlyingErrorKey] = error
}
if let e = error {
delegate?.capture?(capture: self, failedWith: e)
}
}
}
/// A reference to the torchMode.
open var torchMode: AVCaptureTorchMode {
get {
return activeCamera!.torchMode
}
set(value) {
var error: NSError?
if isTorchModeSupported(torchMode: torchMode) {
do {
let device: AVCaptureDevice = activeCamera!
try device.lockForConfiguration()
device.torchMode = value
device.unlockForConfiguration()
} catch let e as NSError {
error = e
}
} else {
var userInfo: Dictionary<String, Any> = Dictionary<String, Any>()
userInfo[NSLocalizedDescriptionKey] = "[Material Error: Unsupported torchMode.]"
userInfo[NSLocalizedFailureReasonErrorKey] = "[Material Error: Unsupported torchMode.]"
error = NSError(domain: "com.cosmicmind.material.capture", code: 0003, userInfo: userInfo)
userInfo[NSUnderlyingErrorKey] = error
}
if let e = error {
delegate?.capture?(capture: self, failedWith: e)
}
}
}
/// The session quality preset.
open var capturePreset = CapturePreset.presetPhoto {
didSet {
session.sessionPreset = CapturePresetToString(preset: capturePreset)
}
}
/// A reference to the previous AVCaptureVideoOrientation.
open fileprivate(set) var previousVideoOrientation: AVCaptureVideoOrientation!
/// The capture video orientation.
open var videoOrientation: AVCaptureVideoOrientation {
var orientation: AVCaptureVideoOrientation
switch UIDevice.current.orientation {
case .portrait:
orientation = .portrait
case .landscapeRight:
orientation = .landscapeLeft
case .portraitUpsideDown:
orientation = .portraitUpsideDown
default:
orientation = .landscapeRight
}
return orientation
}
/// A reference to the captureButton.
@IBInspectable
open var captureButton: UIButton? {
didSet {
prepareCaptureButton()
}
}
/// A reference to the changeModeButton.
@IBInspectable
open var changeModeButton: UIButton? {
didSet {
prepareChangeModeButton()
}
}
/// A reference to the changeCameraButton.
@IBInspectable
open var changeCameraButton: UIButton? {
didSet {
prepareChangeCameraButton()
}
}
/// A reference to the flashButton.
@IBInspectable
open var flashButton: UIButton? {
didSet {
prepareFlashButton()
}
}
/// A boolean indicating whether to enable tap to focus.
@IBInspectable
open var isTapToFocusEnabled = false {
didSet {
guard isTapToFocusEnabled else {
removeTapGesture(gesture: &tapToFocusGesture)
return
}
isTapToResetEnabled = true
prepareTapGesture(gesture: &tapToFocusGesture, numberOfTapsRequired: 1, numberOfTouchesRequired: 1, selector: #selector(handleTapToFocusGesture))
if let v = tapToExposeGesture {
tapToFocusGesture!.require(toFail: v)
}
}
}
/// A boolean indicating whether to enable tap to expose.
@IBInspectable
open var isTapToExposeEnabled = false {
didSet {
guard isTapToExposeEnabled else {
removeTapGesture(gesture: &tapToExposeGesture)
return
}
isTapToResetEnabled = true
prepareTapGesture(gesture: &tapToExposeGesture, numberOfTapsRequired: 2, numberOfTouchesRequired: 1, selector: #selector(handleTapToExposeGesture))
if let v = tapToFocusGesture {
v.require(toFail: tapToExposeGesture!)
}
}
}
/// A boolean indicating whether to enable tap to reset.
@IBInspectable
open var isTapToResetEnabled = false {
didSet {
guard isTapToResetEnabled else {
removeTapGesture(gesture: &tapToResetGesture)
return
}
prepareTapGesture(gesture: &tapToResetGesture, numberOfTapsRequired: 2, numberOfTouchesRequired: 2, selector: #selector(handleTapToResetGesture))
if let v = tapToFocusGesture {
v.require(toFail: tapToResetGesture!)
}
if let v = tapToExposeGesture {
v.require(toFail: tapToResetGesture!)
}
}
}
deinit {
removeOrientationNotifications()
}
/// A convenience initializer.
public convenience init() {
self.init(frame: .zero)
}
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() {
super.prepare()
backgroundColor = .black
prepareSession()
prepareSessionQueue()
prepareActiveVideoInput()
prepareActiveAudioInput()
prepareImageOutput()
prepareMovieOutput()
preparePreview()
prepareOrientationNotifications()
previousVideoOrientation = videoOrientation
isTapToFocusEnabled = true
isTapToExposeEnabled = true
}
}
extension Capture {
/// Prepares self to observe orientation change notifications.
fileprivate func prepareOrientationNotifications() {
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
NotificationCenter.default.addObserver(self, selector: #selector(handleOrientationNotifications(_:)), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}
/// Removes self from observing orientation change notifications.
fileprivate func removeOrientationNotifications() {
UIDevice.current.endGeneratingDeviceOrientationNotifications()
NotificationCenter.default.removeObserver(self)
}
/**
Handler for the captureButton.
- Parameter button: A UIButton that is associated with the event.
*/
@objc
fileprivate func handleOrientationNotifications(_ notification: Notification) {
delegate?.capture?(capture: self, didChangeFrom: previousVideoOrientation, to: videoOrientation)
previousVideoOrientation = videoOrientation
}
}
extension Capture {
/// Prepares the preview.
fileprivate func preparePreview() {
layout(preview).edges()
(preview.layer as! AVCaptureVideoPreviewLayer).session = session
startSession()
}
/// Prepares the captureButton.
fileprivate func prepareCaptureButton() {
captureButton?.addTarget(self, action: #selector(handleCaptureButton(button:)), for: .touchUpInside)
}
/// Prepares the cameraButton.
fileprivate func prepareChangeModeButton() {
changeModeButton?.addTarget(self, action: #selector(handleChangeModeButton(button:)), for: .touchUpInside)
}
/// Prepares the changeCameraButton.
fileprivate func prepareChangeCameraButton() {
changeCameraButton?.addTarget(self, action: #selector(handleChangeCameraButton(button:)), for: .touchUpInside)
}
/// Prepares the flashButton.
fileprivate func prepareFlashButton() {
flashButton?.addTarget(self, action: #selector(handleFlashButton(button:)), for: .touchUpInside)
}
/// Prepares the sessionQueue.
fileprivate func prepareSessionQueue() {
sessionQueue = DispatchQueue(label: "com.cosmicmind.material.capture", attributes: .concurrent, target: nil)
}
/// Prepares the session.
fileprivate func prepareSession() {
session = AVCaptureSession()
}
/// Prepares the activeVideoInput.
fileprivate func prepareActiveVideoInput() {
do {
activeVideoInput = try AVCaptureDeviceInput(device: AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo))
guard session.canAddInput(activeVideoInput) else {
return
}
session.addInput(activeVideoInput)
} catch let e as NSError {
delegate?.capture?(capture: self, failedWith: e)
}
}
/// Prepares the activeAudioInput.
fileprivate func prepareActiveAudioInput() {
do {
activeAudioInput = try AVCaptureDeviceInput(device: AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio))
guard session.canAddInput(activeAudioInput) else {
return
}
session.addInput(activeAudioInput)
} catch let e as NSError {
delegate?.capture?(capture: self, failedWith: e)
}
}
/// Prepares the imageOutput.
fileprivate func prepareImageOutput() {
imageOutput = AVCaptureStillImageOutput()
guard session.canAddOutput(imageOutput) else {
return
}
imageOutput.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG]
session.addOutput(imageOutput)
}
/// Prepares the movieOutput.
fileprivate func prepareMovieOutput() {
movieOutput = AVCaptureMovieFileOutput()
guard session.canAddOutput(movieOutput) else {
return
}
session.addOutput(movieOutput)
}
}
extension Capture {
/// Starts the session.
open func startSession() {
guard !isRunning else {
return
}
sessionQueue.async() { [weak self] in
self?.session.startRunning()
}
}
/// Stops the session.
open func stopSession() {
guard isRunning else {
return
}
sessionQueue.async() { [weak self] in
self?.session.stopRunning()
}
}
/// Changees the camera if possible.
open func changeCamera() {
guard canChangeCamera else {
return
}
do {
guard let v = devicePosition else {
return
}
delegate?.capture?(capture: self, willChangeCamera: v)
let videoInput = try AVCaptureDeviceInput(device: inactiveCamera!)
session.beginConfiguration()
session.removeInput(activeVideoInput)
if session.canAddInput(videoInput) {
session.addInput(videoInput)
activeVideoInput = videoInput
} else {
session.addInput(activeVideoInput)
}
session.commitConfiguration()
delegate?.capture?(capture: self, didChangeCamera: v)
} catch let e as NSError {
delegate?.capture?(capture: self, failedWith: e)
}
}
/// Changees the mode.
open func changeMode() {
delegate?.capture?(capture: self, willChange: mode)
mode = .photo == mode ? .video : .photo
delegate?.capture?(capture: self, didChange: mode)
}
/**
Checks if a given focus mode is supported.
- Parameter focusMode: An AVCaptureFocusMode.
- Returns: A boolean of the result, true if supported, false otherwise.
*/
open func isFocusModeSupported(focusMode: AVCaptureFocusMode) -> Bool {
return activeCamera!.isFocusModeSupported(focusMode)
}
/**
Checks if a given exposure mode is supported.
- Parameter exposureMode: An AVCaptureExposureMode.
- Returns: A boolean of the result, true if supported, false otherwise.
*/
open func isExposureModeSupported(exposureMode: AVCaptureExposureMode) -> Bool {
return activeCamera!.isExposureModeSupported(exposureMode)
}
/**
Checks if a given flash mode is supported.
- Parameter flashMode: An AVCaptureFlashMode.
- Returns: A boolean of the result, true if supported, false otherwise.
*/
open func isFlashModeSupported(flashMode: AVCaptureFlashMode) -> Bool {
return activeCamera!.isFlashModeSupported(flashMode)
}
/**
Checks if a given torch mode is supported.
- Parameter torchMode: An AVCaptureTorchMode.
- Returns: A boolean of the result, true if supported, false otherwise.
*/
open func isTorchModeSupported(torchMode: AVCaptureTorchMode) -> Bool {
return activeCamera!.isTorchModeSupported(torchMode)
}
/**
Focuses the camera at a given point.
- Parameter at: A CGPoint to focus at.
*/
open func focus(at point: CGPoint) {
var error: NSError?
if isFocusPointOfInterestSupported && isFocusModeSupported(focusMode: .autoFocus) {
do {
let device = activeCamera!
try device.lockForConfiguration()
device.focusPointOfInterest = point
device.focusMode = .autoFocus
device.unlockForConfiguration()
} catch let e as NSError {
error = e
}
} else {
var userInfo = [String: Any]()
userInfo[NSLocalizedDescriptionKey] = "[Material Error: Unsupported focus.]"
userInfo[NSLocalizedFailureReasonErrorKey] = "[Material Error: Unsupported focus.]"
error = NSError(domain: "com.cosmicmind.material.capture", code: 0004, userInfo: userInfo)
userInfo[NSUnderlyingErrorKey] = error
}
if let e = error {
delegate?.capture?(capture: self, failedWith: e)
}
}
/**
Exposes the camera at a given point.
- Parameter at: A CGPoint to expose at.
*/
open func expose(at point: CGPoint) {
var error: NSError?
if isExposurePointOfInterestSupported && isExposureModeSupported(exposureMode: .continuousAutoExposure) {
do {
let device = activeCamera!
try device.lockForConfiguration()
device.exposurePointOfInterest = point
device.exposureMode = .continuousAutoExposure
if device.isExposureModeSupported(.locked) {
device.addObserver(self, forKeyPath: "adjustingExposure", options: .new, context: &CaptureAdjustingExposureContext)
}
device.unlockForConfiguration()
} catch let e as NSError {
error = e
}
} else {
var userInfo = [String: Any]()
userInfo[NSLocalizedDescriptionKey] = "[Material Error: Unsupported expose.]"
userInfo[NSLocalizedFailureReasonErrorKey] = "[Material Error: Unsupported expose.]"
error = NSError(domain: "com.cosmicmind.material.capture", code: 0005, userInfo: userInfo)
userInfo[NSUnderlyingErrorKey] = error
}
if let e = error {
delegate?.capture?(capture: self, failedWith: e)
}
}
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if context == &CaptureAdjustingExposureContext {
let device = object as! AVCaptureDevice
if !device.isAdjustingExposure && device.isExposureModeSupported(.locked) {
(object! as AnyObject).removeObserver(self, forKeyPath: "adjustingExposure", context: &CaptureAdjustingExposureContext)
DispatchQueue.main.async { [weak self] in
do {
try device.lockForConfiguration()
device.exposureMode = .locked
device.unlockForConfiguration()
} catch let e as NSError {
guard let s = self else {
return
}
s.delegate?.capture?(capture: s, failedWith: e)
}
}
}
} else {
super.observeValue(forKeyPath: keyPath, of : object, change: change, context: context)
}
}
/**
Resets the camera focus and exposure.
- Parameter focus: A boolean indicating to reset the focus.
- Parameter exposure: A boolean indicating to reset the exposure.
*/
open func reset(focus: Bool = true, exposure: Bool = true) {
let device = activeCamera!
let canResetFocus = device.isFocusPointOfInterestSupported && device.isFocusModeSupported(.continuousAutoFocus)
let canResetExposure = device.isExposurePointOfInterestSupported && device.isExposureModeSupported(.continuousAutoExposure)
let centerPoint = CGPoint(x: 0.5, y: 0.5)
do {
try device.lockForConfiguration()
if canResetFocus && focus {
device.focusMode = .continuousAutoFocus
device.focusPointOfInterest = centerPoint
}
if canResetExposure && exposure {
device.exposureMode = .continuousAutoExposure
device.exposurePointOfInterest = centerPoint
}
device.unlockForConfiguration()
} catch let e as NSError {
delegate?.capture?(capture: self, failedWith: e)
}
}
/// Captures a still image.
open func captureStillImage() {
sessionQueue.async() { [weak self] in
guard let s = self else {
return
}
guard let v = s.imageOutput.connection(withMediaType: AVMediaTypeVideo) else {
return
}
v.videoOrientation = s.videoOrientation
s.imageOutput.captureStillImageAsynchronously(from: v) { [weak self] (sampleBuffer: CMSampleBuffer?, error: Error?) -> Void in
guard let s = self else {
return
}
var captureError = error
if nil == captureError {
let data = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer)!
if let image1 = UIImage(data: data) {
if let image2 = image1.adjustOrientation() {
s.delegate?.capture?(capture: s, asynchronouslyStill: image2)
} else {
var userInfo = [String: Any]()
userInfo[NSLocalizedDescriptionKey] = "[Material Error: Cannot fix image orientation.]"
userInfo[NSLocalizedFailureReasonErrorKey] = "[Material Error: Cannot fix image orientation.]"
captureError = NSError(domain: "com.cosmicmind.material.capture", code: 0006, userInfo: userInfo)
userInfo[NSUnderlyingErrorKey] = error
}
} else {
var userInfo = [String: Any]()
userInfo[NSLocalizedDescriptionKey] = "[Material Error: Cannot capture image from data.]"
userInfo[NSLocalizedFailureReasonErrorKey] = "[Material Error: Cannot capture image from data.]"
captureError = NSError(domain: "com.cosmicmind.material.capture", code: 0007, userInfo: userInfo)
userInfo[NSUnderlyingErrorKey] = error
}
}
if let e = captureError {
s.delegate?.capture?(capture: s, asynchronouslyStillImageFailedWith: e)
}
}
}
}
/// Starts recording.
open func startRecording() {
if !isRecording {
sessionQueue.async() { [weak self] in
guard let s = self else {
return
}
if let v = s.movieOutput.connection(withMediaType: AVMediaTypeVideo) {
v.videoOrientation = s.videoOrientation
v.preferredVideoStabilizationMode = .auto
}
guard let v = s.activeCamera else {
return
}
if v.isSmoothAutoFocusSupported {
do {
try v.lockForConfiguration()
v.isSmoothAutoFocusEnabled = true
v.unlockForConfiguration()
} catch let e as NSError {
s.delegate?.capture?(capture: s, failedWith: e)
}
}
s.movieOutputURL = s.uniqueURL()
if let v = s.movieOutputURL {
s.movieOutput.startRecording(toOutputFileURL: v as URL!, recordingDelegate: s)
}
}
}
}
/// Stops recording.
open func stopRecording() {
guard isRecording else {
return
}
movieOutput.stopRecording()
}
/**
A reference to the camera at a given position, if one exists.
- Parameter at: An AVCaptureDevicePosition.
- Returns: An AVCaptureDevice if one exists, or nil otherwise.
*/
fileprivate func camera(at position: AVCaptureDevicePosition) -> AVCaptureDevice? {
let devices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) as! [AVCaptureDevice]
for device in devices {
if device.position == position {
return device
}
}
return nil
}
/**
Creates a unique URL if possible.
- Returns: A NSURL if it is possible to create one.
*/
fileprivate func uniqueURL() -> URL? {
do {
let directory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .full
dateFormatter.timeStyle = .full
return directory.appendingPathComponent(dateFormatter.string(from: NSDate() as Date) + ".mov")
} catch let e as NSError {
delegate?.capture?(capture: self, createMovieFileFailedWith: e)
}
return nil
}
}
extension Capture {
/**
Handler for the captureButton.
- Parameter button: A UIButton that is associated with the event.
*/
@objc
fileprivate func handleCaptureButton(button: UIButton) {
switch mode {
case .photo:
captureStillImage()
case .video:
if isRecording {
stopRecording()
stopTimer()
} else {
startRecording()
startTimer()
}
}
delegate?.capture?(capture: self, didPressCapture: button)
}
/**
Handler for the changeModeButton.
- Parameter button: A UIButton that is associated with the event.
*/
@objc
fileprivate func handleChangeModeButton(button: UIButton) {
changeMode()
delegate?.capture?(capture: self, didPressChangeMode: button)
}
/**
Handler for the changeCameraButton.
- Parameter button: A UIButton that is associated with the event.
*/
@objc
fileprivate func handleChangeCameraButton(button: UIButton) {
DispatchQueue.main.async { [weak self] in
self?.changeCamera()
}
delegate?.capture?(capture: self, didPressChangeCamera: button)
}
/**
Handler for the flashButton.
- Parameter button: A UIButton that is associated with the event.
*/
@objc
fileprivate func handleFlashButton(button: UIButton) {
delegate?.capture?(capture: self, didPressFlash: button)
}
/**
Handler for the tapToFocusGesture.
- Parameter recognizer: A UITapGestureRecognizer that is associated with the event.
*/
@objc
fileprivate func handleTapToFocusGesture(recognizer: UITapGestureRecognizer) {
guard isTapToFocusEnabled && isFocusPointOfInterestSupported else {
return
}
let point = recognizer.location(in: self)
focus(at: preview.captureDevicePointOfInterestForPoint(point: point))
delegate?.capture?(capture: self, didTapToFocusAt: point)
}
/**
Handler for the tapToExposeGesture.
- Parameter recognizer: A UITapGestureRecognizer that is associated with the event.
*/
@objc
fileprivate func handleTapToExposeGesture(recognizer: UITapGestureRecognizer) {
guard isTapToExposeEnabled && isExposurePointOfInterestSupported else {
return
}
let point = recognizer.location(in: self)
expose(at: preview.captureDevicePointOfInterestForPoint(point: point))
delegate?.capture?(capture: self, didTapToExposeAt: point)
}
/**
Handler for the tapToResetGesture.
- Parameter recognizer: A UITapGestureRecognizer that is associated with the event.
*/
@objc
fileprivate func handleTapToResetGesture(recognizer: UITapGestureRecognizer) {
guard isTapToResetEnabled else {
return
}
reset()
let point = preview.pointForCaptureDevicePointOfInterest(point: CGPoint(x: 0.5, y: 0.5))
delegate?.capture?(capture: self, didTapToResetAt: point)
}
}
extension Capture {
/// Starts the timer for recording.
fileprivate func startTimer() {
timer?.invalidate()
timer = Timer(timeInterval: 0.5, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
RunLoop.main.add(timer!, forMode: .commonModes)
delegate?.capture?(capture: self, didStartRecord: timer!)
}
/// Updates the timer when recording.
@objc
fileprivate func updateTimer() {
let duration = recordedDuration
let time = CMTimeGetSeconds(duration)
let hours = Int(time / 3600)
let minutes = Int((time / 60).truncatingRemainder(dividingBy: 60))
let seconds = Int(time.truncatingRemainder(dividingBy: 60))
delegate?.capture?(capture: self, didUpdateRecord: timer!, hours: hours, minutes: minutes, seconds: seconds)
}
/// Stops the timer when recording.
fileprivate func stopTimer() {
let duration = recordedDuration
let time = CMTimeGetSeconds(duration)
let hours = Int(time / 3600)
let minutes = Int((time / 60).truncatingRemainder(dividingBy: 60))
let seconds = Int(time.truncatingRemainder(dividingBy: 60))
timer?.invalidate()
delegate?.capture?(capture: self, didStopRecord: timer!, hours: hours, minutes: minutes, seconds: seconds)
timer = nil
}
}
extension Capture: UIGestureRecognizerDelegate {
/**
Prepares a given tap gesture.
- Parameter gesture: An optional UITapGestureRecognizer to prepare.
- Parameter numberOfTapsRequired: An integer of the number of taps required
to activate the gesture.
- Parameter numberOfTouchesRequired: An integer of the number of touches, fingers,
required to activate the gesture.
- Parameter selector: A Selector to handle the event.
*/
fileprivate func prepareTapGesture(gesture: inout UITapGestureRecognizer?, numberOfTapsRequired: Int, numberOfTouchesRequired: Int, selector: Selector) {
guard nil == gesture else {
return
}
gesture = UITapGestureRecognizer(target: self, action: selector)
gesture!.delegate = self
gesture!.numberOfTapsRequired = numberOfTapsRequired
gesture!.numberOfTouchesRequired = numberOfTouchesRequired
addGestureRecognizer(gesture!)
}
/**
Removes a given tap gesture.
- Parameter gesture: An optional UITapGestureRecognizer to remove.
*/
fileprivate func removeTapGesture(gesture: inout UITapGestureRecognizer?) {
guard let v = gesture else {
return
}
removeGestureRecognizer(v)
gesture = nil
}
}
extension Capture: AVCaptureFileOutputRecordingDelegate {
public func capture(_ captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) {
isRecording = true
delegate?.capture?(capture: self, captureOutput: captureOutput, didStartRecordingToOutputFileAt: fileURL as NSURL, fromConnections: connections)
}
public func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) {
isRecording = false
delegate?.capture?(capture: self, captureOutput: captureOutput, didFinishRecordingToOutputFileAt: outputFileURL as NSURL, fromConnections: connections, error: error)
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
import AVFoundation
extension UIViewController {
/**
A convenience property that provides access to the CaptureController.
This is the recommended method of accessing the CaptureController
through child UIViewControllers.
*/
public var captureController: CaptureController? {
var viewController: UIViewController? = self
while nil != viewController {
if viewController is CaptureController {
return viewController as? CaptureController
}
viewController = viewController?.parent
}
return nil
}
}
open class CaptureController: ToolbarController {
/// A reference to the Capture instance.
@IBInspectable
open let capture = Capture()
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.portrait
}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return UIInterfaceOrientation.portrait
}
open override func prepare() {
super.prepare()
displayStyle = .full
view.backgroundColor = .black
prepareStatusBar()
prepareToolbar()
prepareCapture()
}
}
extension CaptureController {
/// Prepares the statusBar.
fileprivate func prepareStatusBar() {
statusBar.backgroundColor = .clear
}
/// Prepares the toolbar.
fileprivate func prepareToolbar() {
toolbar.backgroundColor = .clear
toolbar.depthPreset = .none
}
/// Prepares capture.
fileprivate func prepareCapture() {
capture.delegate = self
capture.flashMode = .auto
}
}
extension CaptureController: CaptureDelegate {}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
import AVFoundation
open class CapturePreview: View {
open override class var layerClass: AnyClass {
return AVCaptureVideoPreviewLayer.self
}
/**
Converts a point in layer coordinates to a point of interest
in the coordinate space of the capture device providing input
to the layer.
- Parameter point: A CGPoint.
- Returns: A CGPoint that is converted.
*/
open func captureDevicePointOfInterestForPoint(point: CGPoint) -> CGPoint {
return (layer as! AVCaptureVideoPreviewLayer).captureDevicePointOfInterest(for: point)
}
/**
Converts a point of interest in the coordinate space of the
capture device providing input to the layer to a point in
layer coordinates.
- Parameter point: A CGPoint.
- Returns: A CGPoint that is converted.
*/
open func pointForCaptureDevicePointOfInterest(point: CGPoint) -> CGPoint {
return (layer as! AVCaptureVideoPreviewLayer).pointForCaptureDevicePoint(ofInterest: point)
}
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() {
super.prepare()
preparePreviewLayer()
}
/// Prepares the previewLayer.
private func preparePreviewLayer() {
layer.backgroundColor = Color.black.cgColor
layer.masksToBounds = true
(layer as! AVCaptureVideoPreviewLayer).videoGravity = AVLayerVideoGravityResizeAspectFill
}
}
......@@ -115,12 +115,20 @@ extension CollectionViewLayout {
offset.x += interimSpace
offset.y += interimSpace
if 0 < itemSize.width && 0 < itemSize.height {
if nil != item.width {
offset.x += item.width!
offset.y += item.height!
} else if let v = item.data as? UIView {
} else if let v = item.data as? UIView, 0 < v.bounds.width {
offset.x += v.bounds.width
} else {
offset.x += itemSize.width
}
if nil != item.height {
offset.y += item.height!
} else if let v = item.data as? UIView, 0 < v.bounds.height {
offset.y += v.bounds.height
} else {
offset.y += itemSize.height
}
}
......@@ -148,7 +156,7 @@ extension CollectionViewLayout {
if let h = dataSourceItem.height {
attributes.frame = CGRect(x: contentEdgeInsets.left, y: offset.y, width: collectionView!.bounds.width - contentEdgeInsets.left - contentEdgeInsets.right, height: h)
} else if let v = dataSourceItem.data as? UIView {
} else if let v = dataSourceItem.data as? UIView, 0 < v.bounds.height {
v.setNeedsLayout()
v.layoutIfNeeded()
......@@ -161,7 +169,7 @@ extension CollectionViewLayout {
if let w = dataSourceItem.width {
attributes.frame = CGRect(x: offset.x, y: contentEdgeInsets.top, width: w, height: collectionView!.bounds.height - contentEdgeInsets.top - contentEdgeInsets.bottom)
} else if let v = dataSourceItem.data as? UIView {
} else if let v = dataSourceItem.data as? UIView, 0 < v.bounds.width {
v.setNeedsLayout()
v.layoutIfNeeded()
......
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import EventKit
import CoreData
@objc(EventsReminderAuthorizationStatus)
public enum EventsReminderAuthorizationStatus: Int {
case authorized
case denied
}
@objc(EventsReminderPriority)
public enum EventsReminderPriority: Int {
case none
case high = 1
case medium = 5
case low = 9
}
@objc(EventsDelegate)
public protocol EventsDelegate {
/**
A delegation method that is executed when the reminder authorization
status changes.
- Parameter events: A reference to the Events instance.
- Parameter status: A reference to the EventReminderAuthorizationStatus.
*/
@objc
optional func events(events: Events, status: EventsReminderAuthorizationStatus)
/**
A delegation method that is fired when changes to the event store occur.
- Parameter events: A reference to the Events instance.
*/
@objc
optional func eventsShouldRefresh(events: Events)
/**
A delegation method that is executed when events authorization is authorized.
- Parameter events: A reference to the Events instance.
*/
@objc
optional func eventsAuthorizedForReminders(events: Events)
/**
A delegation method that is executed when events authorization is denied.
- Parameter events: A reference to the Events instance.
*/
@objc
optional func eventsDeniedForReminders(events: Events)
/**
A delegation method that is executed when a new calendar is created.
- Parameter events: A reference to the Events instance.
- Parameter createdCalendar calendar: An optional reference to the calendar created.
- Parameter error: An optional error if the calendar failed to be created.
*/
@objc
optional func events(events: Events, createdCalendar calendar: EKCalendar?, error: Error?)
/**
A delegation method that is executed when a calendar is updated.
- Parameter events: A reference to the Events instance.
- Parameter updatedCalendar calendar: A reference to the updated calendar.
- Parameter error: An optional error if the calendar failed to be updated.
*/
@objc
optional func events(events: Events, updatedCalendar calendar: EKCalendar, error: Error?)
/**
A delegation method that is executed when a calendar is removed.
- Parameter events: A reference to the Events instance.
- Parameter removedCalendar calendar: A reference to the calendar removed.
- Parameter error: An optional error if the calendar failed to be removed.
*/
@objc
optional func events(events: Events, removedCalendar calendar: EKCalendar, error: Error?)
/**
A delegation method that is executed when a new reminder is created.
- Parameter events: A reference to the Events instance.
- Parameter createdReminder reminder: An optional reference to the reminder created.
- Parameter error: An optional error if the reminder failed to be created.
*/
@objc
optional func events(events: Events, createdReminder reminder: EKReminder?, error: Error?)
/**
A delegation method that is executed when a reminder is updated.
- Parameter events: A reference to the Events instance.
- Parameter updatedReminder reminder: A reference to the updated reminder.
- Parameter error: An optional error if the reminder failed to be updated.
*/
@objc
optional func events(events: Events, updatedReminder reminder: EKReminder, error: Error?)
/**
A delegation method that is executed when a reminder is removed.
- Parameter events: A reference to the Events instance.
- Parameter removedReminder reminder: A reference to the removed reminder.
- Parameter error: An optional error if the reminder failed to be removed.
*/
@objc
optional func events(events: Events, removedReminder reminder: EKReminder, error: Error?)
}
@objc(Events)
open class Events: NSObject {
/// A cache of calendars.
open fileprivate(set) var cacheForCalendars = [AnyHashable: EKCalendar]()
/// A cache of reminders.
open fileprivate(set) var cacheForReminders = [AnyHashable: EKReminder]()
/// A boolean indicating whether to commit saves or not.
fileprivate var isCommitted = true
/// A reference to the eventsStore.
fileprivate let eventStore = EKEventStore()
/// The current EventsReminderAuthorizationStatus.
open var authorizationStatusForReminders: EventsReminderAuthorizationStatus {
return .authorized == EKEventStore.authorizationStatus(for: .reminder) ? .authorized : .denied
}
/// A reference to an EventsDelegate.
open weak var delegate: EventsDelegate?
/// Denitializer.
deinit {
NotificationCenter.default.removeObserver(self)
}
/**
Requests authorization for reminders.
- Parameter completion: An optional completion callback.
*/
open func requestAuthorizationForReminders(completion: ((EventsReminderAuthorizationStatus) -> Void)? = nil) {
eventStore.requestAccess(to: .reminder) { [weak self, completion = completion] (isAuthorized, _) in
DispatchQueue.main.async { [weak self, completion = completion] in
guard let s = self else {
return
}
guard isAuthorized else {
completion?(.denied)
s.delegate?.events?(events: s, status: .denied)
s.delegate?.eventsDeniedForReminders?(events: s)
return
}
s.prepareNotification()
completion?(.authorized)
s.delegate?.events?(events: s, status: .authorized)
s.delegate?.eventsAuthorizedForReminders?(events: s)
}
}
}
}
extension Events {
/// Prepares the notification handlers.
fileprivate func prepareNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(handleEventStoreChange(_:)), name: NSNotification.Name.EKEventStoreChanged, object: eventStore)
}
}
extension Events {
/**
Handler for event store changes.
- Parameter _ notification: A Notification.
*/
@objc
fileprivate func handleEventStoreChange(_ notification: Notification) {
delegate?.eventsShouldRefresh?(events: self)
}
}
extension Events {
/// Begins a storage transaction.
open func begin() {
isCommitted = false
}
/// Resets the storage transaction state.
open func reset() {
isCommitted = true
}
/**
Commits the storage transaction.
- Parameter completion: A completion call back.
*/
open func commit(_ completion: ((Bool, Error?) -> Void)) {
reset()
var success = false
var error: Error?
do {
try eventStore.commit()
success = true
} catch let e {
error = e
}
completion(success, error)
}
}
extension Events {
/**
Creates a predicate for the events Array of calendars.
- Parameter in calendars: An optional Array of EKCalendars.
*/
open func predicateForReminders(in calendars: [EKCalendar]) -> NSPredicate {
return eventStore.predicateForReminders(in: calendars)
}
/**
Creates a predicate with a given start and end date for
incomplete reminders. Providing a calendars Array narrows
the search.
- Parameter starting: A Date.
- Parameter ending: A Date.
- Parameter calendars: An optional Array of [EKCalendar].
*/
open func predicateForIncompleteReminders(starting: Date, ending: Date, calendars: [EKCalendar]? = nil) -> NSPredicate {
return eventStore.predicateForIncompleteReminders(withDueDateStarting: starting, ending: ending, calendars: calendars)
}
/**
Creates a predicate with a given start and end date for
completed reminders. Providing a calendars Array narrows
the search.
- Parameter starting: A Date.
- Parameter ending: A Date.
- Parameter calendars: An optional Array of [EKCalendar].
*/
open func predicateForCompletedReminders(starting: Date, ending: Date, calendars: [EKCalendar]? = nil) -> NSPredicate {
return eventStore.predicateForCompletedReminders(withCompletionDateStarting: starting, ending: ending, calendars: calendars)
}
}
extension Events {
/**
Fetches all calendars for a given reminder.
- Parameter completion: A completion call back
*/
open func fetchCalendarsForReminders(_ completion: @escaping ([EKCalendar]) -> Void) {
DispatchQueue.global(qos: .default).async { [weak self, completion = completion] in
guard let s = self else {
return
}
let calendars = s.eventStore.calendars(for: .reminder).sorted(by: { (a, b) -> Bool in
return a.title < b.title
})
for calendar in calendars {
s.cacheForCalendars[calendar.calendarIdentifier] = calendar
}
DispatchQueue.main.async { [calendars = calendars, completion = completion] in
completion(calendars)
}
}
}
/**
Fetches all reminders matching a given predicate.
- Parameter predicate: A NSPredicate.
- Parameter completion: A completion call back.
- Returns: A fetch events request identifier.
*/
@discardableResult
open func fetchReminders(matching predicate: NSPredicate, completion: @escaping ([EKReminder]) -> Void) -> Any {
return eventStore.fetchReminders(matching: predicate, completion: { [weak self, completion = completion] (reminders) in
guard let s = self else {
return
}
let r = reminders ?? []
for reminder in r {
s.cacheForReminders[reminder.calendarItemIdentifier] = reminder
}
DispatchQueue.main.async { [completion = completion] in
completion(r)
}
})
}
/**
Fetch all the events in a given Array of calendars.
- Parameter in calendars: An Array of EKCalendars.
- Parameter completion: A completion call back.
- Returns: A fetch events request identifier.
*/
@discardableResult
open func fetchReminders(in calendars: [EKCalendar], completion: @escaping ([EKReminder]) -> Void) -> Any {
return fetchReminders(matching: predicateForReminders(in: calendars), completion: completion)
}
/**
Fetch all the events in a given Array of calendars that
are incomplete, given a start and end date.
- Parameter starting: A Date.
- Parameter ending: A Date.
- Parameter calendars: An Array of EKCalendars.
- Parameter completion: A completion call back.
- Returns: A fetch events request identifier.
*/
@discardableResult
open func fetchIncompleteReminders(starting: Date, ending: Date, calendars: [EKCalendar]? = nil, completion: @escaping ([EKReminder]) -> Void) -> Any {
return fetchReminders(matching: predicateForIncompleteReminders(starting: starting, ending: ending, calendars: calendars), completion: completion)
}
/**
Fetch all the events in a given Array of calendars that
are completed, given a start and end date.
- Parameter starting: A Date.
- Parameter ending: A Date.
- Parameter calendars: An Array of EKCalendars.
- Parameter completion: A completion call back.
- Returns: A fetch events request identifier.
*/
@discardableResult
open func fetchCompletedReminders(starting: Date, ending: Date, calendars: [EKCalendar]? = nil, completion: @escaping ([EKReminder]) -> Void) -> Any {
return fetchReminders(matching: predicateForCompletedReminders(starting: starting, ending: ending, calendars: calendars), completion: completion)
}
/**
Cancels an active events request.
- Parameter _ identifier: An identifier.
*/
open func cancelFetchRequest(_ identifier: Any) {
eventStore.cancelFetchRequest(identifier)
}
}
extension Events {
/**
Creates a new reminder calendar.
- Parameter calendar title: the name of the list.
- Parameter completion: An optional completion call back.
*/
open func createCalendarForReminders(title: String, completion: ((EKCalendar?, Error?) -> Void)? = nil) {
DispatchQueue.global(qos: .default).async { [weak self, completion = completion] in
guard let s = self else {
return
}
let calendar = EKCalendar(for: .reminder, eventStore: s.eventStore)
calendar.title = title
calendar.source = s.eventStore.defaultCalendarForNewReminders().source
var success = false
var error: Error?
do {
try s.eventStore.saveCalendar(calendar, commit: s.isCommitted)
success = true
s.cacheForCalendars[calendar.calendarIdentifier] = calendar
} catch let e {
error = e
}
DispatchQueue.main.async { [weak self, calendar = calendar, error = error, completion = completion] in
guard let s = self else {
return
}
completion?(success ? calendar : nil, error)
s.delegate?.events?(events: s, createdCalendar: success ? calendar : nil, error: error)
}
}
}
/**
Updates a given calendar.
- Parameter calendar: An EKCalendar.
- Parameter completion: An optional completion call back.
*/
open func update(calendar: EKCalendar, completion: ((Bool, Error?) -> Void)? = nil) {
DispatchQueue.global(qos: .default).async { [weak self, calendar = calendar, completion = completion] in
guard let s = self else {
return
}
var success = false
var error: Error?
do {
try s.eventStore.saveCalendar(calendar, commit: s.isCommitted)
success = true
s.cacheForCalendars[calendar.calendarIdentifier] = calendar
} catch let e {
error = e
}
DispatchQueue.main.async { [weak self, calendar = calendar, error = error, completion = completion] in
guard let s = self else {
return
}
completion?(success, error)
s.delegate?.events?(events: s, updatedCalendar: calendar, error: error)
}
}
}
/**
Removes an existing calendar,
- Parameter calendar identifier: The EKCalendar identifier String.
- Parameter completion: An optional completion call back.
*/
open func removeCalendar(identifier: String, completion: ((Bool, Error?) -> Void)? = nil) {
DispatchQueue.global(qos: .default).async { [weak self, completion = completion] in
guard let s = self else {
return
}
var success = false
var error: Error?
guard let calendar = s.eventStore.calendar(withIdentifier: identifier) else {
var userInfo = [String: Any]()
userInfo[NSLocalizedDescriptionKey] = "[Material Error: Cannot remove calendar with identifier \(identifier).]"
userInfo[NSLocalizedFailureReasonErrorKey] = "[Material Error: Cannot remove calendar with identifier \(identifier).]"
error = NSError(domain: "com.cosmicmind.material.events", code: 0001, userInfo: userInfo)
completion?(success, error)
return
}
do {
let calendarIdentifier = calendar.calendarIdentifier
try s.eventStore.removeCalendar(calendar, commit: s.isCommitted)
success = true
s.cacheForCalendars[calendarIdentifier] = nil
} catch let e {
error = e
}
DispatchQueue.main.async { [weak self, calendar = calendar, error = error, completion = completion] in
guard let s = self else {
return
}
completion?(success, error)
s.delegate?.events?(events: s, removedCalendar: calendar, error: error)
}
}
}
}
extension Events {
/**
Adds a new reminder to an optionally existing list.
if the list does not exist it will be added to the default events list.
- Parameter title: A String.
- Parameter calendar: An EKCalendar.
- Parameter startDateComponents: An optional DateComponents.
- Parameter dueDateComponents: An optional DateComponents.
- Parameter priority: An optional EventsReminderPriority.
- Parameter completion: An optional completion call back.
*/
open func createReminder(title: String, calendar: EKCalendar, startDateComponents: DateComponents? = nil, dueDateComponents: DateComponents? = nil, priority: EventsReminderPriority? = .none, notes: String?, completion: ((EKReminder?, Error?) -> Void)? = nil) {
DispatchQueue.global(qos: .default).async { [weak self, calendar = calendar, completion = completion] in
guard let s = self else {
return
}
let reminder = EKReminder(eventStore: s.eventStore)
reminder.title = title
reminder.calendar = calendar
reminder.startDateComponents = startDateComponents
reminder.dueDateComponents = dueDateComponents
reminder.priority = priority?.rawValue ?? EventsReminderPriority.none.rawValue
reminder.notes = notes
var success = false
var error: Error?
do {
try s.eventStore.save(reminder, commit: s.isCommitted)
success = true
s.cacheForReminders[reminder.calendarItemIdentifier] = reminder
} catch let e {
error = e
}
DispatchQueue.main.async { [weak self, reminder = reminder, error = error, completion = completion] in
guard let s = self else {
return
}
completion?(success ? reminder : nil, error)
s.delegate?.events?(events: s, createdReminder: success ? reminder : nil, error: error)
}
}
}
/**
Updates a given reminder.
- Parameter reminder: An EKReminder.
- Parameter completion: An optional completion call back.
*/
open func update(reminder: EKReminder, completion: ((Bool, Error?) -> Void)? = nil) {
DispatchQueue.global(qos: .default).async { [weak self, reminder = reminder, completion = completion] in
guard let s = self else {
return
}
var success = false
var error: Error?
do {
try s.eventStore.save(reminder, commit: s.isCommitted)
success = true
s.cacheForReminders[reminder.calendarItemIdentifier] = reminder
} catch let e {
error = e
}
DispatchQueue.main.async { [weak self, reminder = reminder, error = error, completion = completion] in
guard let s = self else {
return
}
completion?(success, error)
s.delegate?.events?(events: s, updatedReminder: reminder, error: error)
}
}
}
/**
Removes an existing reminder,
- Parameter reminder identifier: The EKReminders identifier String.
- Parameter completion: An optional completion call back.
*/
open func removeReminder(identifier: String, completion: ((Bool, Error?) -> Void)? = nil) {
DispatchQueue.global(qos: .default).async { [weak self, completion = completion] in
guard let s = self else {
return
}
var success = false
var error: Error?
guard let reminder = s.eventStore.calendarItem(withIdentifier: identifier) as? EKReminder else {
var userInfo = [String: Any]()
userInfo[NSLocalizedDescriptionKey] = "[Material Error: Cannot remove reminder with identifier \(identifier).]"
userInfo[NSLocalizedFailureReasonErrorKey] = "[Material Error: Cannot remove reminder with identifier \(identifier).]"
error = NSError(domain: "com.cosmicmind.material.events", code: 0002, userInfo: userInfo)
completion?(success, error)
return
}
do {
let calendarItemIdentifier = reminder.calendarItemIdentifier
try s.eventStore.remove(reminder, commit: s.isCommitted)
success = true
s.cacheForReminders[calendarItemIdentifier] = nil
} catch let e {
error = e
}
DispatchQueue.main.async { [weak self, reminder = reminder, error = error, completion = completion] in
guard let s = self else {
return
}
completion?(success, error)
s.delegate?.events?(events: s, removedReminder: reminder, error: error)
}
}
}
}
extension Events {
/**
Creates an alarm using the current time plus a given timeInterval.
- Parameter timeIntervalSinceNow: A TimeInterval.
- Returns: An EKAlarm.
*/
open func createAlarm(timeIntervalSinceNow: TimeInterval) -> EKAlarm {
return EKAlarm(absoluteDate: Date(timeIntervalSinceNow: timeIntervalSinceNow))
}
/**
Creates an alarm using given date components.
- Parameter day: An optional Int.
- Parameter month: An optional Int.
- Parameter year: An optional Int.
- Parameter hour: An optional Int.
- Parameter minute: An optional Int.
- Parameter second: An optional Int.
- Returns: An optional EKAlarm.
*/
open func createAlarm(day: Int? = nil, month: Int? = nil, year: Int? = nil, hour: Int? = nil, minute: Int? = nil, second: Int? = nil) -> EKAlarm {
var dateComponents = DateComponents()
dateComponents.calendar = Calendar.current
dateComponents.day = day
dateComponents.month = month
dateComponents.year = year
dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.second = second
return EKAlarm(absoluteDate: dateComponents.date!)
}
/**
Creates an alarm using a relative offset from the start date.
- Parameter relativeOffset offset: A TimeInterval.
- Returns: An EKAlarm.
*/
open func createAlarm(relativeOffset offset: TimeInterval) -> EKAlarm {
return EKAlarm(relativeOffset: offset)
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
extension UIViewController {
/**
A convenience property that provides access to the EventsController.
This is the recommended method of accessing the EventsController
through child UIViewControllers.
*/
public var eventsController: EventsController? {
var viewController: UIViewController? = self
while nil != viewController {
if viewController is EventsController {
return viewController as? EventsController
}
viewController = viewController?.parent
}
return nil
}
}
open class EventsController: UIViewController {
/// A reference to an Events instance.
open let events = Events()
open override func viewDidLoad() {
super.viewDidLoad()
prepare()
}
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepareView method
to initialize property values and other setup operations.
The super.prepareView method should always be called immediately
when subclassing.
*/
open func prepare() {
view.clipsToBounds = true
view.backgroundColor = .white
view.contentScaleFactor = Screen.scale
prepareEvents()
}
}
extension EventsController {
/// Prepares the events instance.
fileprivate func prepareEvents() {
events.delegate = self
}
}
extension EventsController: EventsDelegate {}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Photos
@objc(PhotoLibraryMove)
public class PhotoLibraryMove: NSObject {
/// An index that is being moved from.
public private(set) var from: Int
/// An index that is being moved to.
public private(set) var to: Int
/**
An initializer that accepts a `from` and `to` Int value.
- Parameter from: An Int.
- Parameter to: An Int.
*/
public init(from: Int, to: Int) {
self.from = from
self.to = to
}
}
public struct PhotoLibraryFetchResultDataSource {
/// A reference to the PHFetchResults.
public internal(set) var fetchResult: PHFetchResult<PHObject>
/// A reference to the objects associated with the PHFetchResult.
public internal(set) var objects: [PHObject]
}
@objc(PhotoLibraryDelegate)
public protocol PhotoLibraryDelegate {
/**
A delegation method that is executed when the PhotoLibrary status is updated.
- Parameter photoLibrary: A reference to the PhotoLibrary.
- Parameter status: A reference to the PHAuthorizationStatus.
*/
@objc
optional func photoLibrary(photoLibrary: PhotoLibrary, status: PHAuthorizationStatus)
/**
A delegation method that is executed when the PhotoLibrary is authorized.
- Parameter photoLibrary: A reference to the PhotoLibrary.
*/
@objc
optional func photoLibrary(authorized photoLibrary: PhotoLibrary)
/**
A delegation method that is executed when the PhotoLibrary is denied.
- Parameter photoLibrary: A reference to the PhotoLibrary.
*/
@objc
optional func photoLibrary(denied photoLibrary: PhotoLibrary)
/**
A delegation method that is executed when the PhotoLibrary is not determined.
- Parameter photoLibrary: A reference to the PhotoLibrary.
*/
@objc
optional func photoLibrary(notDetermined photoLibrary: PhotoLibrary)
/**
A delegation method that is executed when the PhotoLibrary is restricted.
- Parameter photoLibrary: A reference to the PhotoLibrary.
*/
@objc
optional func photoLibrary(restricted photoLibrary: PhotoLibrary)
/**
A delegation method that is executed when the PhotoLibrary has changes,
locally or remotely.
- Parameter photoLibrary: A reference to the PhotoLibrary.
- Parameter changeInfo: A reference to a PHChange object.
*/
@objc
optional func photoLibrary(photoLibrary: PhotoLibrary, didChange changeInfo: PHChange)
/**
A delegation method that is executed when changes are detected.
- Parameter photoLibrary: A reference to the PhotoLibrary.
- Parameter beforeChanges: A PHObject before changes.
- Parameter afterChanges: A PHObject after changes.
- Parameter assetContentChanged: A Bool that is true if the image or video content for this
object has changed.
- Parameter objectWasDeleted: A Bool that is true if the object was deleted.
*/
@objc
optional func photoLibrary(photoLibrary: PhotoLibrary, beforeChanges: PHObject, afterChanges: PHObject, assetContentChanged: Bool, objectWasDeleted: Bool)
/**
A delegation method that is executed when there is a change in the
fetchResult object.
- Parameter photoLibrary: A reference to the PhotoLibrary.
- Parameter fetchBeforeChanges: A PHFetchResult<PHObject> before changes.
- Parameter fetchAfterChanges: A PHFetchResult<PHObject> after changes.
changes exist. True if yes, false otherwise.
*/
@objc
optional func photoLibrary(photoLibrary: PhotoLibrary, fetchBeforeChanges: PHFetchResult<PHObject>, fetchAfterChanges: PHFetchResult<PHObject>)
/**
A delegation method that is executed when there are moved objects.
- Parameter photoLibrary: A reference to the PhotoLibrary.
- Parameter removed indexes: An IndexSet of the removed indexes.
- Parameter for objects: An Array of PHObjects that have been removed.
*/
@objc
optional func photoLibrary(photoLibrary: PhotoLibrary, removed indexes: IndexSet, for objects: [PHObject])
/**
A delegation method that is executed when there are newly inserted objects.
- Parameter photoLibrary: A reference to the PhotoLibrary.
- Parameter inserted indexes: An IndexSet of the inserted indexes.
- Parameter for objects: An Array of PHObjects that have been inserted.
*/
@objc
optional func photoLibrary(photoLibrary: PhotoLibrary, inserted indexes: IndexSet, for objects: [PHObject])
/**
A delegation method that is executed when there are changed objects.
- Parameter photoLibrary: A reference to the PhotoLibrary.
- Parameter changed indexes: An IndexSet of the changed indexes.
- Parameter for objects: An Array of PHObjects that have been changed.
*/
@objc
optional func photoLibrary(photoLibrary: PhotoLibrary, changed indexes: IndexSet, for objects: [PHObject])
/**
A delegation method that is executed describing the removed, inserted
and changed indexes.
- Parameter photoLibrary: A reference to the PhotoLibrary.
- Parameter removedIndexes: An IndexSet of the changed indexes.
- Parameter insertedIndexes: An IndexSet of the inserted indexes.
- Parameter changedIndexes: An IndexSet of the changed indexes.
- Parameter has moves: An Array of move coordinates.
*/
@objc
optional func photoLibrary(photoLibrary: PhotoLibrary, removedIndexes: IndexSet?, insertedIndexes: IndexSet?, changedIndexes: IndexSet?, has moves: [PhotoLibraryMove])
}
@objc(PhotoLibrary)
public class PhotoLibrary: NSObject {
/// A reference to the PHCachingImageManager.
public internal(set) lazy var cachingImageManager = PHCachingImageManager()
/// A reference to all current PHFetchResults.
public internal(set) lazy var fetchResultsDataSource = [String: PhotoLibraryFetchResultDataSource]()
/// A reference to a PhotoLibraryDelegate.
open weak var delegate: PhotoLibraryDelegate?
/// The current PHAuthorizationStatus.
public var authorizationStatus: PHAuthorizationStatus {
return PHPhotoLibrary.authorizationStatus()
}
/// Deinitializer that unregisters itself from watching changes in the PHPhotoLibrary.
deinit {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
/// An initializer that prepares the PhotoLibrary.
public override init() {
super.init()
prepare()
}
/// Prepare the instance object.
open func prepare() {
prepareChangeObservers()
}
/// Prepares the PHPhotoLibrary change observation.
private func prepareChangeObservers() {
PHPhotoLibrary.shared().register(self)
}
}
/// Authorization.
extension PhotoLibrary {
/**
A method to request authorization from the user to enable photo library access. In order
for this to work, set the "Privacy - Photo Library Usage Description" value in the
application's info.plist.
- Parameter _ completion: A completion block that passes in a PHAuthorizationStatus
enum that describes the response for the authorization request.
*/
public func requestAuthorization(_ completion: ((PHAuthorizationStatus) -> Void)? = nil) {
PHPhotoLibrary.requestAuthorization { [weak self, completion = completion] (status) in
DispatchQueue.main.async { [weak self, completion = completion] in
guard let s = self else {
return
}
switch status {
case .authorized:
s.delegate?.photoLibrary?(photoLibrary: s, status: .authorized)
s.delegate?.photoLibrary?(authorized: s)
completion?(.authorized)
case .denied:
s.delegate?.photoLibrary?(photoLibrary: s, status: .denied)
s.delegate?.photoLibrary?(denied: s)
completion?(.denied)
case .notDetermined:
s.delegate?.photoLibrary?(photoLibrary: s, status: .notDetermined)
s.delegate?.photoLibrary?(notDetermined: s)
completion?(.notDetermined)
case .restricted:
s.delegate?.photoLibrary?(photoLibrary: s, status: .restricted)
s.delegate?.photoLibrary?(restricted: s)
completion?(.restricted)
}
}
}
}
}
/// Fetch.
extension PhotoLibrary {
/**
Fetches generic type T for a given PHFetchResult<T>.
- Parameter fetchResult: A PHFetchResult<T>.
- Parameter completion: A completion block.
*/
@discardableResult
internal func fetch<T: PHObject, U: PHFetchResult<T>>(caller: String, result: U, completion: (([T], U) -> Void)? = nil) -> [T] {
var objects = [T]()
result.enumerateObjects({ (collection, _, _) in
objects.append(collection)
})
// Used in change observation.
fetchResultsDataSource[caller] = PhotoLibraryFetchResultDataSource(fetchResult: result as! PHFetchResult<PHObject>, objects: objects)
if Thread.isMainThread {
completion?(objects, result)
} else {
DispatchQueue.main.async { [objects = objects, result = result, completion = completion] in
completion?(objects, result)
}
}
return objects
}
}
/// PHCollectionList.
extension PhotoLibrary {
/**
A PHAssetCollectionMoment collection type will be contained
by a PHCollectionListSubtypeMomentListCluster and a
PHCollectionListSubtypeMomentListYear. Non-moment PHAssetCollections
will only be contained by a single collection list.
- Parameter _ collection: A PHCollection.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion callback.
*/
@discardableResult
public func fetchCollectionListsContaining(_ collection: PHCollection, options: PHFetchOptions?, completion: (([PHCollectionList], PHFetchResult<PHCollectionList>) -> Void)? = nil) -> [PHCollectionList] {
return fetch(caller: #function, result: PHCollectionList.fetchCollectionListsContaining(collection, options: options), completion: completion)
}
/**
Fetch PHCollectionLists based on a type and subtype.
- Parameter with type: A PHCollectionListType.
- Parameter subtype: A PHCollectionListSubtype.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion callback.
*/
@discardableResult
public func fetchCollectionList(with type: PHCollectionListType, subtype: PHCollectionListSubtype, options: PHFetchOptions?, completion: (([PHCollectionList], PHFetchResult<PHCollectionList>) -> Void)? = nil) -> [PHCollectionList] {
return fetch(caller: #function, result: PHCollectionList.fetchCollectionLists(with: type, subtype: subtype, options: options), completion: completion)
}
/**
Fetch collection lists of a single type matching the
provided local identifiers (type is inferred from the
local identifiers).
- Parameter withLocalIdentifier identifiers: An Array
of String identifiers.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion callback.
*/
@discardableResult
public func fetchCollectionLists(withLocalIdentifiers identifiers: [String], options: PHFetchOptions?, completion: (([PHCollectionList], PHFetchResult<PHCollectionList>) -> Void)? = nil) -> [PHCollectionList] {
return fetch(caller: #function, result: PHCollectionList.fetchCollectionLists(withLocalIdentifiers: identifiers, options: options), completion: completion)
}
/**
Fetch asset collections of a single type and subtype
provided (use PHCollectionListSubtypeAny to match all
subtypes).
- Parameter with collectionListType: A PHCollectionListType.
- Parameter subtype: A PHCollectionListSubtype.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion callback.
*/
@discardableResult
public func fetchCollectionLists(with collectionListType: PHCollectionListType, subtype: PHCollectionListSubtype, options: PHFetchOptions?, completion: (([PHCollectionList], PHFetchResult<PHCollectionList>) -> Void)? = nil) -> [PHCollectionList] {
return fetch(caller: #function, result: PHCollectionList.fetchCollectionLists(with: collectionListType, subtype: subtype, options: options), completion: completion)
}
/**
Fetch moment lists containing a given moment.
- Parameter with momentListSubtype: A PHCollectionListSubtype.
- Parameter containingMoment moment: A PHAssetCollection.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion callback.
*/
@discardableResult
public func fetchMomentLists(with momentListSubtype: PHCollectionListSubtype, containingMoment moment: PHAssetCollection, options: PHFetchOptions?, completion: (([PHCollectionList], PHFetchResult<PHCollectionList>) -> Void)? = nil) -> [PHCollectionList] {
return fetch(caller: #function, result: PHCollectionList.fetchMomentLists(with: momentListSubtype, containingMoment: moment, options: options), completion: completion)
}
/**
Fetch moment lists.
- Parameter with momentListSubtype: A PHCollectionListSubtype.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion callback.
*/
@discardableResult
public func fetchMomentLists(with momentListSubtype: PHCollectionListSubtype, options: PHFetchOptions?, completion: (([PHCollectionList], PHFetchResult<PHCollectionList>) -> Void)? = nil) -> [PHCollectionList] {
return fetch(caller: #function, result: PHCollectionList.fetchMomentLists(with: momentListSubtype, options: options), completion: completion)
}
}
/// PHCollection.
extension PhotoLibrary {
/**
Fetches PHCollections in a given PHCollectionList.
- Parameter in collectionList: A PHCollectionList.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion callback.
*/
@discardableResult
public func fetchCollections(in collectionList: PHCollectionList, options: PHFetchOptions?, completion: (([PHCollection], PHFetchResult<PHCollection>) -> Void)? = nil) -> [PHCollection] {
return fetch(caller: #function, result: PHCollection.fetchCollections(in: collectionList, options: options), completion: completion)
}
/**
Fetches PHCollections based on a type and subtype.
- Parameter with type: A PHCollectionListType.
- Parameter subtype: A PHCollectionListSubtype.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion callback.
*/
@discardableResult
public func fetchTopLevelUserCollections(with options: PHFetchOptions?, completion: (([PHCollection], PHFetchResult<PHCollection>) -> Void)? = nil) -> [PHCollection] {
return fetch(caller: #function, result: PHCollection.fetchTopLevelUserCollections(with: nil), completion: completion)
}
}
/// PHAssetCollection.
extension PhotoLibrary {
/**
Fetch asset collections of a single type matching the provided
local identifiers (type is inferred from the local identifiers).
- Parameter withLocalIdentifiers identifiers: An Array of Strings.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion block.
*/
@discardableResult
public func fetchAssetCollections(withLocalIdentifiers identifiers: [String], options: PHFetchOptions?, completion: (([PHAssetCollection], PHFetchResult<PHAssetCollection>) -> Void)? = nil) -> [PHAssetCollection] {
return fetch(caller: #function, result: PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: identifiers, options: options), completion: completion)
}
/**
Fetch asset collections of a single type and subtype provided
(use PHAssetCollectionSubtypeAny to match all subtypes).
- Parameter with type: A PHAssetCollection.
- Parameter subtype: A PHAssetCollectionSubtype.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion block.
*/
@discardableResult
public func fetchAssetCollections(with type: PHAssetCollectionType, subtype: PHAssetCollectionSubtype, options: PHFetchOptions?, completion: (([PHAssetCollection], PHFetchResult<PHAssetCollection>) -> Void)? = nil) -> [PHAssetCollection] {
return fetch(caller: #function, result: PHAssetCollection.fetchAssetCollections(with: type, subtype: subtype, options: options), completion: completion)
}
/**
Smart Albums are not supported, only Albums and Moments.
- Parameter asset: A PHAsset.
- Parameter with type: A PHAssetCollection.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion block.
*/
@discardableResult
public func fetchAssetCollectionsContaining(_ asset: PHAsset, with type: PHAssetCollectionType, options: PHFetchOptions?, completion: (([PHAssetCollection], PHFetchResult<PHAssetCollection>) -> Void)? = nil) -> [PHAssetCollection] {
return fetch(caller: #function, result: PHAssetCollection.fetchAssetCollectionsContaining(asset, with: type, options: options), completion: completion)
}
/**
AssetGroupURLs are URLs retrieved from ALAssetGroup's
ALAssetsGroupPropertyURL.
- Parameter withALAssetGroupURLs assetGroupURLs: An Array
of URLs.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion block.
*/
@discardableResult
public func fetchAssetCollections(withALAssetGroupURLs assetGroupURLs: [URL], options: PHFetchOptions?, completion: (([PHAssetCollection], PHFetchResult<PHAssetCollection>) -> Void)? = nil) -> [PHAssetCollection] {
return fetch(caller: #function, result: PHAssetCollection.fetchAssetCollections(withALAssetGroupURLs: assetGroupURLs, options: options), completion: completion)
}
/**
Fetches moments in a given moment list.
- Parameter inMomentList momentList: A PHCollectionList.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion block.
*/
@discardableResult
public func fetchMoments(inMomentList momentList: PHCollectionList, options: PHFetchOptions?, completion: (([PHAssetCollection], PHFetchResult<PHAssetCollection>) -> Void)? = nil) -> [PHAssetCollection] {
return fetch(caller: #function, result: PHAssetCollection.fetchMoments(inMomentList: momentList, options: options), completion: completion)
}
/**
Fetches moments.
- Parameter with options: An optional PHFetchOptions object.
- Parameter completion: A completion block.
*/
@discardableResult
public func fetchMoments(with options: PHFetchOptions?, completion: (([PHAssetCollection], PHFetchResult<PHAssetCollection>) -> Void)? = nil) -> [PHAssetCollection] {
return fetch(caller: #function, result: PHAssetCollection.fetchMoments(with: options), completion: completion)
}
}
/// PHAsset.
extension PhotoLibrary {
/**
Fetch the PHAssets in a given PHAssetCollection.
- Parameter in assetCollection: A PHAssetCollection.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion block.
*/
@discardableResult
public func fetchAssets(in assetCollection: PHAssetCollection, options: PHFetchOptions?, completion: (([PHAsset], PHFetchResult<PHAsset>) -> Void)? = nil) -> [PHAsset] {
return fetch(caller: #function, result: PHAsset.fetchAssets(in: assetCollection, options: options), completion: completion)
}
/**
Fetch the PHAssets with a given Array of identifiers.
- Parameter withLocalIdentifiers identifiers: A Array of
String identifiers.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion block.
*/
@discardableResult
public func fetchAssets(withLocalIdentifiers identifiers: [String], options: PHFetchOptions?, completion: (([PHAsset], PHFetchResult<PHAsset>) -> Void)? = nil) -> [PHAsset] {
return fetch(caller: #function, result: PHAsset.fetchAssets(withLocalIdentifiers: identifiers, options: options), completion: completion)
}
/**
Fetch key assets.
- Parameter in assetCollection: A PHAssetCollection.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion block.
- Returns: An optional PHFetchResult<PHAsset> object.
*/
@discardableResult
public func fetchKeyAssets(in assetCollection: PHAssetCollection, options: PHFetchOptions?, completion: (([PHAsset], PHFetchResult<PHAsset>) -> Void)? = nil) -> PHFetchResult<PHAsset>? {
guard let fetchResult = PHAsset.fetchKeyAssets(in: assetCollection, options: options) else {
return nil
}
let _: [PHAsset] = fetch(caller: #function, result: fetchResult, completion: completion)
return fetchResult
}
/**
Fetch a burst asset with a given burst identifier.
- Parameter withBurstIdentifier burstIdentifier: A
PHAssetCollection.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion block.
*/
@discardableResult
public func fetchAssets(withBurstIdentifier burstIdentifier: String, options: PHFetchOptions?, completion: (([PHAsset], PHFetchResult<PHAsset>) -> Void)? = nil) -> [PHAsset] {
return fetch(caller: #function, result: PHAsset.fetchAssets(withBurstIdentifier: burstIdentifier, options: options), completion: completion)
}
/**
Fetches PHAssetSourceTypeUserLibrary assets by default (use
includeAssetSourceTypes option to override).
- Parameter with options: An optional PHFetchOptions object.
- Parameter completion: A completion block.
*/
@discardableResult
public func fetchAssets(with options: PHFetchOptions?, completion: (([PHAsset], PHFetchResult<PHAsset>) -> Void)? = nil) -> [PHAsset] {
return fetch(caller: #function, result: PHAsset.fetchAssets(with: options), completion: completion)
}
/**
Fetch the PHAssets with a given media type.
- Parameter in mediaType: A PHAssetMediaType.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion block.
*/
@discardableResult
public func fetchAssets(with mediaType: PHAssetMediaType, options: PHFetchOptions?, completion: (([PHAsset], PHFetchResult<PHAsset>) -> Void)? = nil) -> [PHAsset] {
return fetch(caller: #function, result: PHAsset.fetchAssets(with: mediaType, options: options), completion: completion)
}
/**
AssetURLs are URLs retrieved from ALAsset's
ALAssetPropertyAssetURL.
- Parameter withALAssetURLs assetURLs: An Array of URLs.
- Parameter options: An optional PHFetchOptions object.
- Parameter completion: A completion block.
*/
@discardableResult
public func fetchAssets(withALAssetURLs assetURLs: [URL], options: PHFetchOptions?, completion: (([PHAsset], PHFetchResult<PHAsset>) -> Void)? = nil) -> [PHAsset] {
return fetch(caller: #function, result: PHAsset.fetchAssets(withALAssetURLs: assetURLs, options: options), completion: completion)
}
}
/// PHImageManager.
extension PhotoLibrary {
/**
Retrieves an optional UIImage for a given PHAsset that allows for a targetSize
and contentMode.
- Parameter for asset: A PHAsset.
- Parameter targetSize: A CGSize.
- Parameter contentMode: A PHImageContentMode.
- Parameter options: A PHImageRequestOptions.
- Parameter completion: A completion block.
- Returns: A PHImageRequestID.
*/
@discardableResult
public func requestImage(for asset: PHAsset, targetSize: CGSize, contentMode: PHImageContentMode, options: PHImageRequestOptions?, completion: @escaping (UIImage?, [AnyHashable: Any]?) -> Void) -> PHImageRequestID {
return PHImageManager.default().requestImage(for: asset, targetSize: targetSize, contentMode: contentMode, options: options, resultHandler: completion)
}
/**
Retrieves an optional Data object for a given PHAsset.
- Parameter for asset: A PHAsset.
- Parameter options: A PHImageRequestOptions.
- Parameter completion: A completion block.
- Returns: A PHImageRequestID.
*/
@discardableResult
public func requestImageData(for asset: PHAsset, options: PHImageRequestOptions?, completion: @escaping (Data?, String?, UIImageOrientation, [AnyHashable: Any]?) -> Void) -> PHImageRequestID {
return PHImageManager.default().requestImageData(for: asset, options: options, resultHandler: completion)
}
/**
Cancels an image request for a given PHImageRequestID.
- Parameter for requestID: A PHImageRequestID.
*/
public func cancelImageRequest(for requestID: PHImageRequestID) {
PHImageManager.default().cancelImageRequest(requestID)
}
/**
Requests a live photo representation of the asset. With
oportunistic (or if no
options are specified), the resultHandler block may be
called more than once (the first call may occur before
the method returns). The PHImageResultIsDegradedKey key
in the result handler's info parameter indicates when a
temporary low-quality live photo is provided.
- Parameter for asset: A PHAsset.
- Parameter targetSize: A CGSize.
- Parameter contentMode: A PHImageContentMode.
- Parameter options: A PHImageRequestOptions.
- Parameter completion: A completion block.
- Returns: A PHImageRequestID.
*/
@available(iOS 9.1, *)
@discardableResult
public func requestLivePhoto(for asset: PHAsset, targetSize: CGSize, contentMode: PHImageContentMode, options: PHLivePhotoRequestOptions?, completion: @escaping (PHLivePhoto?, [AnyHashable : Any]?) -> Void) -> PHImageRequestID {
return PHImageManager.default().requestLivePhoto(for: asset, targetSize: targetSize, contentMode: contentMode, options: options, resultHandler: completion)
}
/**
For playback only.
- Parameter forVideo asset: A PHAsset.
- Parameter options: A PHImageRequestOptions.
- Parameter completion: A completion block.
- Returns: A PHImageRequestID.
*/
@discardableResult
public func requestPlayerItem(forVideo asset: PHAsset, options: PHVideoRequestOptions?, completion: @escaping (AVPlayerItem?, [AnyHashable : Any]?) -> Swift.Void) -> PHImageRequestID {
return PHImageManager.default().requestPlayerItem(forVideo: asset, options: options, resultHandler: completion)
}
/**
Export.
- Parameter forVideo asset: A PHAsset.
- Parameter options: A PHImageRequestOptions.
- Parameter completion: A completion block.
- Returns: A PHImageRequestID.
*/
@discardableResult
public func requestExportSession(forVideo asset: PHAsset, options: PHVideoRequestOptions?, exportPreset: String, completion: @escaping (AVAssetExportSession?, [AnyHashable : Any]?) -> Void) -> PHImageRequestID {
return PHImageManager.default().requestExportSession(forVideo: asset, options: options, exportPreset: exportPreset, resultHandler: completion)
}
/**
For all other requests.
- Parameter forVideo asset: A PHAsset.
- Parameter options: A PHImageRequestOptions.
- Parameter completion: A completion block.
- Returns: A PHImageRequestID.
*/
@discardableResult
public func requestAVAsset(forVideo asset: PHAsset, options: PHVideoRequestOptions?, completion: @escaping (AVAsset?, AVAudioMix?, [AnyHashable : Any]?) -> Void) -> PHImageRequestID {
return PHImageManager.default().requestAVAsset(forVideo: asset, options: options, resultHandler: completion)
}
}
/// PHPhotoLibraryChangeObserver extension.
extension PhotoLibrary: PHPhotoLibraryChangeObserver {
/**
A delegation method that is fired when changes are made in the photo library.
- Parameter _ changeInstance: A PHChange obejct describing the changes in the
photo library.
*/
public func photoLibraryDidChange(_ changeInfo: PHChange) {
DispatchQueue.main.async { [weak self, changeInfo = changeInfo] in
guard let s = self else {
return
}
s.delegate?.photoLibrary?(photoLibrary: s, didChange: changeInfo)
for (_, var dataSource) in s.fetchResultsDataSource {
for i in 0..<dataSource.objects.count {
let object = dataSource.objects[i]
guard let details = changeInfo.changeDetails(for: object) else {
continue
}
guard let afterChanges = details.objectAfterChanges else {
continue
}
dataSource.objects[i] = afterChanges
s.delegate?.photoLibrary?(photoLibrary: s, beforeChanges: details.objectBeforeChanges, afterChanges: afterChanges, assetContentChanged: details.assetContentChanged, objectWasDeleted: details.objectWasDeleted)
}
if let details = changeInfo.changeDetails(for: dataSource.fetchResult) {
s.delegate?.photoLibrary?(photoLibrary: s, fetchBeforeChanges: details.fetchResultBeforeChanges, fetchAfterChanges: details.fetchResultAfterChanges)
dataSource.fetchResult = details.fetchResultAfterChanges
guard details.hasIncrementalChanges else {
continue
}
let removedIndexes = details.removedIndexes
let insertedIndexes = details.insertedIndexes
let changedIndexes = details.changedIndexes
if nil != removedIndexes {
s.delegate?.photoLibrary?(photoLibrary: s, removed: removedIndexes!, for: details.removedObjects)
}
if nil != insertedIndexes {
s.delegate?.photoLibrary?(photoLibrary: s, inserted: insertedIndexes!, for: details.insertedObjects)
}
if nil != changedIndexes {
s.delegate?.photoLibrary?(photoLibrary: s, changed: changedIndexes!, for: details.changedObjects)
}
var moves = [PhotoLibraryMove]()
if details.hasMoves {
details.enumerateMoves { (from, to) in
moves.append(PhotoLibraryMove(from: from, to: to))
}
}
s.delegate?.photoLibrary?(photoLibrary: s, removedIndexes: removedIndexes, insertedIndexes: insertedIndexes, changedIndexes: changedIndexes, has: moves)
}
}
}
}
}
/// Requesting changes.
extension PhotoLibrary {
/**
Performes an asynchronous change to the PHPhotoLibrary database.
- Parameter _ block: A transactional block that ensures that
all changes to the PHPhotoLibrary are atomic.
- Parameter completion: A completion block that is executed once the
transaction has been completed.
*/
public func performChanges(_ block: @escaping () -> Void, completion: ((Bool, Error?) -> Void)? = nil) {
PHPhotoLibrary.shared().performChanges(block, completionHandler: completion)
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
extension UIViewController {
/**
A convenience property that provides access to the PhotoLibraryController.
This is the recommended method of accessing the PhotoLibraryController
through child UIViewControllers.
*/
public var photoLibraryController: PhotoLibraryController? {
var viewController: UIViewController? = self
while nil != viewController {
if viewController is PhotoLibraryController {
return viewController as? PhotoLibraryController
}
viewController = viewController?.parent
}
return nil
}
}
open class PhotoLibraryController: UIViewController {
/// A reference to a PhotoLibrary.
open let photoLibrary = PhotoLibrary()
open override func viewDidLoad() {
super.viewDidLoad()
prepare()
}
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open func prepare() {
view.clipsToBounds = true
view.backgroundColor = .white
view.contentScaleFactor = Screen.scale
preparePhotoLibrary()
}
}
extension PhotoLibraryController {
/// Prepares the photoLibrary.
fileprivate func preparePhotoLibrary() {
photoLibrary.delegate = self
}
}
extension PhotoLibraryController: PhotoLibraryDelegate {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment