Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e298925f6 | |||
| 363816d6ad | |||
| 9d7f65772b | |||
| cef6a6188a | |||
| 1515f6761b | |||
| 8fee841003 | |||
| ee85248b23 | |||
| c135346125 | |||
| bd7ee080bd | |||
| 3929205637 |
@@ -7,3 +7,9 @@ Examples/SimpleVideoExport/SimpleVideoExport.xcodeproj/project.xcworkspace/xcuse
|
||||
Examples/SimpleRealtimeFilterPlayback/SimpleRealtimeFilterPlayback.xcodeproj/xcuserdata
|
||||
Examples/SimpleVideoTransition/SimpleVideoTransition.xcodeproj/project.xcworkspace/xcuserdata/renzhumacro.xcuserdatad
|
||||
Examples/SimpleVideoTransition/SimpleVideoTransition.xcodeproj/xcuserdata/renzhumacro.xcuserdatad
|
||||
Examples/SimpleVideoAnimation/Pods/Pods.xcodeproj/xcuserdata/renzhumacro.xcuserdatad
|
||||
Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/project.xcworkspace/xcuserdata
|
||||
Examples/SimpleVideoAnimation/SimpleVIdeoAnimation.xcodeproj/xcuserdata
|
||||
Examples/SimpleVideoAnimation/SimpleVideoAnimation.xcworkspace/xcuserdata
|
||||
Examples/SimpleVideoTransitionSwitch/SimpleVideoTransitionSwitch.xcodeproj/xcuserdata
|
||||
Examples/SimpleVideoEditor/SimpleVideoEditor.xcodeproj/project.xcworkspace/xcuserdata
|
||||
|
||||
@@ -367,7 +367,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = RYRPKMVKDL;
|
||||
DEVELOPMENT_TEAM = 3BJYKWHMM4;
|
||||
INFOPLIST_FILE = SimpleFourSquareVideo/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -385,7 +385,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = RYRPKMVKDL;
|
||||
DEVELOPMENT_TEAM = 3BJYKWHMM4;
|
||||
INFOPLIST_FILE = SimpleFourSquareVideo/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
||||
@@ -37,13 +37,17 @@ class ViewController: UIViewController {
|
||||
let backgroundFrame = self.renderView.frame
|
||||
let backgroundSize = CGSize(width: backgroundFrame.size.width * 1.5,
|
||||
height: backgroundFrame.size.height * 1.5)
|
||||
MetalVideoProcessBackground.canvasSize = backgroundSize
|
||||
MetalVideoProcessBackground.canvasSize = backgroundSize
|
||||
|
||||
|
||||
let transform1 = MetalVideoProcessTransformFilter()
|
||||
let transform2 = MetalVideoProcessTransformFilter()
|
||||
let transform3 = MetalVideoProcessTransformFilter()
|
||||
let transform4 = MetalVideoProcessTransformFilter()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
transform1.stretchType = .aspectToFit
|
||||
transform1.roi = CGRect(x: 0.1, y: 0.1,
|
||||
width: 0.8,
|
||||
@@ -79,37 +83,49 @@ class ViewController: UIViewController {
|
||||
player.addTarget(transform4, trackID: item4.trackID, targetTrackId: item4.trackID)
|
||||
|
||||
transform1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID)
|
||||
|
||||
//
|
||||
transform2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID)
|
||||
|
||||
//
|
||||
transform3.saveUniformSettings(forTimelineRange: item3.timeRange, trackID: item3.trackID)
|
||||
|
||||
//
|
||||
transform4.saveUniformSettings(forTimelineRange: item4.timeRange, trackID: item4.trackID)
|
||||
|
||||
let layer1 = MetalVideoProcessBlendFilter()
|
||||
|
||||
background --> layer1
|
||||
transform1 --> layer1
|
||||
|
||||
// layer1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID)
|
||||
|
||||
let layer2 = MetalVideoProcessBlendFilter()
|
||||
|
||||
layer1 --> layer2
|
||||
transform2 --> layer2
|
||||
|
||||
//
|
||||
// layer2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID)
|
||||
//
|
||||
let layer3 = MetalVideoProcessBlendFilter()
|
||||
|
||||
layer2 --> layer3
|
||||
transform3 --> layer3
|
||||
|
||||
// layer3.saveUniformSettings(forTimelineRange: item3.timeRange, trackID: item3.trackID)
|
||||
|
||||
let layer4 = MetalVideoProcessBlendFilter()
|
||||
|
||||
layer3 --> layer4
|
||||
transform4 --> layer4
|
||||
|
||||
// layer4.saveUniformSettings(forTimelineRange: item4.timeRange, trackID: item4.trackID)
|
||||
//
|
||||
|
||||
self.progress.minimumValue = 0.0
|
||||
self.progress.maximumValue = Float(playerItem.duration.seconds)
|
||||
|
||||
|
||||
|
||||
layer4 --> renderView
|
||||
// layer3 --> self.renderView
|
||||
// transform1 --> self.renderView
|
||||
|
||||
self.player = player
|
||||
self.player?.playerDelegate = self
|
||||
|
||||
|
||||
+6
-2
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
36E063AD2523E25200CCC13B /* PlaygroundVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E063AC2523E25200CCC13B /* PlaygroundVC.swift */; };
|
||||
6ADB075A24ADDB0C0010A817 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADB075924ADDB0C0010A817 /* AppDelegate.swift */; };
|
||||
6ADB075C24ADDB0C0010A817 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADB075B24ADDB0C0010A817 /* SceneDelegate.swift */; };
|
||||
6ADB075E24ADDB0C0010A817 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADB075D24ADDB0C0010A817 /* ViewController.swift */; };
|
||||
@@ -51,6 +52,7 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
36E063AC2523E25200CCC13B /* PlaygroundVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaygroundVC.swift; sourceTree = "<group>"; };
|
||||
6ADB075624ADDB0C0010A817 /* SimpleRealtimeFilterPlayback.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimpleRealtimeFilterPlayback.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6ADB075924ADDB0C0010A817 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
6ADB075B24ADDB0C0010A817 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
@@ -100,6 +102,7 @@
|
||||
6ADB075924ADDB0C0010A817 /* AppDelegate.swift */,
|
||||
6ADB075B24ADDB0C0010A817 /* SceneDelegate.swift */,
|
||||
6ADB075D24ADDB0C0010A817 /* ViewController.swift */,
|
||||
36E063AC2523E25200CCC13B /* PlaygroundVC.swift */,
|
||||
6ADB075F24ADDB0C0010A817 /* Main.storyboard */,
|
||||
6ADB07DB24AED8910010A817 /* 853.mp4 */,
|
||||
6ADB07D924AED8220010A817 /* cute.mp4 */,
|
||||
@@ -219,6 +222,7 @@
|
||||
6ADB075E24ADDB0C0010A817 /* ViewController.swift in Sources */,
|
||||
6ADB075A24ADDB0C0010A817 /* AppDelegate.swift in Sources */,
|
||||
6ADB075C24ADDB0C0010A817 /* SceneDelegate.swift in Sources */,
|
||||
36E063AD2523E25200CCC13B /* PlaygroundVC.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -371,7 +375,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = RYRPKMVKDL;
|
||||
DEVELOPMENT_TEAM = 3BJYKWHMM4;
|
||||
INFOPLIST_FILE = SimpleRealtimeFilterPlayback/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -389,7 +393,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = RYRPKMVKDL;
|
||||
DEVELOPMENT_TEAM = 3BJYKWHMM4;
|
||||
INFOPLIST_FILE = SimpleRealtimeFilterPlayback/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
||||
+145
-22
@@ -1,12 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="omE-bu-A38">
|
||||
<device id="retina5_9" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="ngz-2E-aVN">
|
||||
<objects>
|
||||
<navigationController id="omE-bu-A38" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="d7c-cd-KMe">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="kKn-PK-K0V" kind="relationship" relationship="rootViewController" id="yse-EQ-xX6"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="daM-ut-0wO" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-1237" y="-69"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
@@ -16,94 +34,95 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Dc5-Dz-opT" customClass="MetalVideoProcessRenderView" customModule="MetalVideoProcess">
|
||||
<rect key="frame" x="0.0" y="44.000000000000028" width="375" height="468.66666666666674"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<rect key="frame" x="0.0" y="88.000000000000028" width="375" height="468.66666666666674"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="Dc5-Dz-opT" secondAttribute="height" multiplier="0.8" id="86r-Og-HwX"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="vYk-6H-dKP">
|
||||
<rect key="frame" x="8" y="517.66666666666663" width="359" height="31"/>
|
||||
<rect key="frame" x="8" y="561.66666666666663" width="359" height="31"/>
|
||||
<connections>
|
||||
<action selector="progressChanged:" destination="BYZ-38-t0r" eventType="valueChanged" id="fwh-uk-uYl"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="K6w-Ud-xUK">
|
||||
<rect key="frame" x="172.66666666666666" y="552.66666666666663" width="30" height="30"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="K6w-Ud-xUK">
|
||||
<rect key="frame" x="172.66666666666666" y="596.66666666666663" width="30" height="30"/>
|
||||
<state key="normal" title="Play"/>
|
||||
<connections>
|
||||
<action selector="play:" destination="BYZ-38-t0r" eventType="touchUpInside" id="cZ4-2E-l3J"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Blur track1:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ieB-aM-Q6C">
|
||||
<rect key="frame" x="20" y="663.66666666666663" width="108" height="21"/>
|
||||
<rect key="frame" x="20" y="707.66666666666663" width="107" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Gray video:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8Il-25-Tij">
|
||||
<rect key="frame" x="104" y="709.66666666666663" width="87" height="21"/>
|
||||
<rect key="frame" x="104" y="753.66666666666663" width="87" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Beauty track1:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9fu-53-ZHp">
|
||||
<rect key="frame" x="20" y="622.66666666666663" width="109" height="21"/>
|
||||
<rect key="frame" x="20" y="666.66666666666663" width="109" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="track2:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Y6k-OT-zIp">
|
||||
<rect key="frame" x="208" y="622.66666666666663" width="54" height="21"/>
|
||||
<rect key="frame" x="208" y="666.66666666666663" width="54" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="track2:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wCL-xj-Wni">
|
||||
<rect key="frame" x="207" y="663.66666666666663" width="54" height="21"/>
|
||||
<rect key="frame" x="206" y="707.66666666666663" width="54" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="Lbm-mS-3au">
|
||||
<rect key="frame" x="139" y="617.66666666666663" width="51" height="31"/>
|
||||
<rect key="frame" x="139" y="661.66666666666663" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="filterOn:" destination="BYZ-38-t0r" eventType="valueChanged" id="f2R-IT-cw2"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<switch opaque="NO" tag="1" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="rJO-mU-xYY">
|
||||
<rect key="frame" x="138" y="658.66666666666663" width="51" height="31"/>
|
||||
<rect key="frame" x="137" y="702.66666666666663" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="filterOn:" destination="BYZ-38-t0r" eventType="valueChanged" id="yx1-uC-eEF"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<switch opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="eJu-z5-owd">
|
||||
<rect key="frame" x="272" y="617.66666666666663" width="51" height="31"/>
|
||||
<rect key="frame" x="272" y="661.66666666666663" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="filterOn:" destination="BYZ-38-t0r" eventType="valueChanged" id="9Y9-ZR-JFD"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<switch opaque="NO" tag="3" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="Ufa-eY-5ZC">
|
||||
<rect key="frame" x="271" y="658.66666666666663" width="51" height="31"/>
|
||||
<rect key="frame" x="270" y="702.66666666666663" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="filterOn:" destination="BYZ-38-t0r" eventType="valueChanged" id="Bsk-J9-CjP"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<switch opaque="NO" tag="4" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="tbD-8b-LUS">
|
||||
<rect key="frame" x="201" y="704.66666666666663" width="51" height="31"/>
|
||||
<rect key="frame" x="201" y="748.66666666666663" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="filterOn:" destination="BYZ-38-t0r" eventType="valueChanged" id="J3N-K6-5di"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gMy-fV-lJB">
|
||||
<rect key="frame" x="166.66666666666666" y="587.66666666666663" width="42" height="30"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gMy-fV-lJB">
|
||||
<rect key="frame" x="166.66666666666666" y="631.66666666666663" width="42" height="30"/>
|
||||
<state key="normal" title="Pause"/>
|
||||
<connections>
|
||||
<action selector="pause:" destination="BYZ-38-t0r" eventType="touchUpInside" id="WbY-hn-U3f"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="eJu-z5-owd" firstAttribute="leading" secondItem="Y6k-OT-zIp" secondAttribute="trailing" constant="10" id="0Ih-iu-pAo"/>
|
||||
<constraint firstItem="Y6k-OT-zIp" firstAttribute="centerY" secondItem="Lbm-mS-3au" secondAttribute="centerY" id="5T7-S9-kXH"/>
|
||||
@@ -136,8 +155,8 @@
|
||||
<constraint firstItem="9fu-53-ZHp" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="20" id="ynQ-x2-1Fe"/>
|
||||
<constraint firstItem="eJu-z5-owd" firstAttribute="centerY" secondItem="Y6k-OT-zIp" secondAttribute="centerY" id="z7O-GK-or8"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="INN-Cx-VbS"/>
|
||||
<connections>
|
||||
<outlet property="progress" destination="vYk-6H-dKP" id="qNJ-VV-aFL"/>
|
||||
<outlet property="renderView" destination="Dc5-Dz-opT" id="LzR-68-614"/>
|
||||
@@ -145,7 +164,111 @@
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="131.8840579710145" y="137.94642857142856"/>
|
||||
<point key="canvasLocation" x="1065" y="411"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="l41-w0-TrY">
|
||||
<objects>
|
||||
<viewController id="kKn-PK-K0V" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Bjp-hz-Fn2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ZjZ-Ux-Ghm">
|
||||
<rect key="frame" x="148.66666666666666" y="391" width="78" height="30"/>
|
||||
<state key="normal" title="Playground"/>
|
||||
<connections>
|
||||
<segue destination="afR-es-Hbs" kind="show" id="A6B-e2-0n4"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FMM-3O-BgN">
|
||||
<rect key="frame" x="151" y="431" width="73" height="30"/>
|
||||
<state key="normal" title="MultiVideo"/>
|
||||
<connections>
|
||||
<segue destination="BYZ-38-t0r" kind="show" id="WXt-uP-yZo"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="l6C-up-lB1"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="FMM-3O-BgN" firstAttribute="top" secondItem="ZjZ-Ux-Ghm" secondAttribute="bottom" constant="10" id="3qm-yn-BcB"/>
|
||||
<constraint firstItem="FMM-3O-BgN" firstAttribute="centerX" secondItem="Bjp-hz-Fn2" secondAttribute="centerX" id="8Km-bN-wp1"/>
|
||||
<constraint firstItem="ZjZ-Ux-Ghm" firstAttribute="centerX" secondItem="Bjp-hz-Fn2" secondAttribute="centerX" id="8MN-4O-p65"/>
|
||||
<constraint firstItem="ZjZ-Ux-Ghm" firstAttribute="centerY" secondItem="Bjp-hz-Fn2" secondAttribute="centerY" id="b6n-6P-sux"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="UVZ-3x-Uyx"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="yNO-yo-vzK" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-404" y="-69.458128078817737"/>
|
||||
</scene>
|
||||
<!--PlaygroundVC-->
|
||||
<scene sceneID="5qv-Wk-ccO">
|
||||
<objects>
|
||||
<viewController id="afR-es-Hbs" customClass="PlaygroundVC" customModule="SimpleRealtimeFilterPlayback" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="2uZ-cD-AQm">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="T1T-WM-jRk" customClass="MetalVideoProcessRenderView" customModule="MetalVideoProcess">
|
||||
<rect key="frame" x="0.0" y="88.000000000000028" width="375" height="468.66666666666674"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="T1T-WM-jRk" secondAttribute="height" multiplier="0.8" id="j6X-Xj-fIT"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="m9P-aS-DOe">
|
||||
<rect key="frame" x="8" y="561.66666666666663" width="359" height="31"/>
|
||||
<connections>
|
||||
<action selector="progressChanged:" destination="afR-es-Hbs" eventType="valueChanged" id="fgH-WE-XlN"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="j6P-6j-YVr">
|
||||
<rect key="frame" x="172.66666666666666" y="596.66666666666663" width="30" height="30"/>
|
||||
<state key="normal" title="Play"/>
|
||||
<connections>
|
||||
<action selector="play:" destination="afR-es-Hbs" eventType="touchUpInside" id="ruz-mE-tpy"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vAH-WV-nnK">
|
||||
<rect key="frame" x="166.66666666666666" y="631.66666666666663" width="42" height="30"/>
|
||||
<state key="normal" title="Pause"/>
|
||||
<connections>
|
||||
<action selector="pause:" destination="afR-es-Hbs" eventType="touchUpInside" id="IsH-CI-uaA"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="law-Lw-xq9"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="T1T-WM-jRk" firstAttribute="leading" secondItem="law-Lw-xq9" secondAttribute="leading" id="0g4-SY-2dL"/>
|
||||
<constraint firstItem="j6P-6j-YVr" firstAttribute="top" secondItem="m9P-aS-DOe" secondAttribute="bottom" constant="5" id="50E-VL-Gdw"/>
|
||||
<constraint firstItem="T1T-WM-jRk" firstAttribute="top" secondItem="law-Lw-xq9" secondAttribute="top" id="5rf-K8-jDy"/>
|
||||
<constraint firstItem="vAH-WV-nnK" firstAttribute="centerX" secondItem="2uZ-cD-AQm" secondAttribute="centerX" id="5uI-W3-4Qf"/>
|
||||
<constraint firstItem="m9P-aS-DOe" firstAttribute="leading" secondItem="law-Lw-xq9" secondAttribute="leading" constant="10" id="999-hW-XHL"/>
|
||||
<constraint firstItem="law-Lw-xq9" firstAttribute="trailing" secondItem="m9P-aS-DOe" secondAttribute="trailing" constant="10" id="Gr4-Q1-U2S"/>
|
||||
<constraint firstItem="law-Lw-xq9" firstAttribute="trailing" secondItem="T1T-WM-jRk" secondAttribute="trailing" id="IWm-ud-ETw"/>
|
||||
<constraint firstItem="vAH-WV-nnK" firstAttribute="top" secondItem="j6P-6j-YVr" secondAttribute="bottom" constant="5" id="LQP-Bv-GJ9"/>
|
||||
<constraint firstItem="m9P-aS-DOe" firstAttribute="top" secondItem="T1T-WM-jRk" secondAttribute="bottom" constant="5" id="S4w-RT-SbR"/>
|
||||
<constraint firstItem="j6P-6j-YVr" firstAttribute="centerX" secondItem="2uZ-cD-AQm" secondAttribute="centerX" id="XHf-mP-3mo"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="Aqz-co-Lc4"/>
|
||||
<connections>
|
||||
<outlet property="progress" destination="m9P-aS-DOe" id="Jjh-H5-Qsf"/>
|
||||
<outlet property="renderView" destination="T1T-WM-jRk" id="QEN-az-weS"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="6bn-ay-7JA" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1066" y="-263"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Use</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// PlaygroundVC.swift
|
||||
// SimpleRealtimeFilterPlayback
|
||||
//
|
||||
// Created by wangrenzhu on 2020/9/30.
|
||||
// Copyright © 2020 RenZhu Macro. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
import MetalVideoProcess
|
||||
|
||||
|
||||
class PlaygroundVC: UIViewController {
|
||||
|
||||
@IBOutlet weak var renderView: MetalVideoProcessRenderView!
|
||||
@IBOutlet weak var progress: UISlider!
|
||||
|
||||
var grayFilter: MetalVideoProcessLuminance?
|
||||
var player: MetalVideoProcessPlayer?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
let asset1 = AVAsset(url: Bundle.main.url(forResource: "853", withExtension: "mp4")!)
|
||||
let item1 = MetalVideoEditorItem(asset: asset1)
|
||||
|
||||
do {
|
||||
let editor = try MetalVideoEditor(videoItems: [item1],
|
||||
customVideoCompositorClass: MetalVideoProcessCompositor.self)
|
||||
|
||||
let playerItem = editor.buildPlayerItem()
|
||||
self.progress.maximumValue = Float(playerItem.duration.seconds)
|
||||
let player = try MetalVideoProcessPlayer(playerItem: playerItem)
|
||||
|
||||
let gray = MetalVideoProcessLuminance()
|
||||
gray.saveUniformSettings(forTimelineRange: CMTimeRange(start: CMTime(seconds: 0.5), duration: CMTime(seconds: 3.0)), trackID: item1.trackID)
|
||||
gray.isEnable = true
|
||||
self.grayFilter = gray
|
||||
|
||||
player.addTarget(gray, atTargetIndex: nil, trackID: item1.trackID, targetTrackId: 0)
|
||||
gray --> renderView
|
||||
|
||||
player.playerDelegate = self
|
||||
self.player = player
|
||||
} catch {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
self.player?.suspend()
|
||||
self.player?.dispose()
|
||||
}
|
||||
|
||||
@IBAction func play(_ sender: Any) {
|
||||
self.player?.play()
|
||||
}
|
||||
|
||||
@IBAction func pause(_ sender: Any) {
|
||||
self.player?.pause()
|
||||
}
|
||||
|
||||
@IBAction func progressChanged(_ sender: UISlider) {
|
||||
let value = sender.value
|
||||
self.player?.seekTo(time: Float64(value))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension PlaygroundVC: MetalVideoProcessPlayerDelegate {
|
||||
func playbackFrameTimeChanged(frameTime time: CMTime, player: AVPlayer) {
|
||||
DispatchQueue.main.async {
|
||||
self.progress.value = Float(time.seconds)
|
||||
}
|
||||
}
|
||||
|
||||
func playEnded(currentPlayer player: AVPlayer) {
|
||||
|
||||
}
|
||||
|
||||
func finishExport(error: NSError?) {
|
||||
|
||||
}
|
||||
|
||||
func exportProgressChanged(_ progress: Float) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Uncomment the next line to define a global platform for your project
|
||||
# platform :ios, '9.0'
|
||||
|
||||
target 'SimpleVideoAnimation' do
|
||||
# Comment the next line if you don't want to use dynamic frameworks
|
||||
use_frameworks!
|
||||
|
||||
# Pods for SimpleVideoAnimation
|
||||
pod 'PryntTrimmerView'
|
||||
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
PODS:
|
||||
- PryntTrimmerView (4.0.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- PryntTrimmerView
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- PryntTrimmerView
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
PryntTrimmerView: 67e67573ffd4c073f1308effa66aefd0cfa667a0
|
||||
|
||||
PODFILE CHECKSUM: 4473f02a1528566660f9c5ed79ea1d9d7df2096d
|
||||
|
||||
COCOAPODS: 1.8.4
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
PODS:
|
||||
- PryntTrimmerView (4.0.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- PryntTrimmerView
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- PryntTrimmerView
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
PryntTrimmerView: 67e67573ffd4c073f1308effa66aefd0cfa667a0
|
||||
|
||||
PODFILE CHECKSUM: 4473f02a1528566660f9c5ed79ea1d9d7df2096d
|
||||
|
||||
COCOAPODS: 1.8.4
|
||||
@@ -0,0 +1,632 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
032F877127CB819B89EEF1CD59F35190 /* AssetVideoScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A4A1BACF84E64074643AF16313BED24 /* AssetVideoScrollView.swift */; };
|
||||
084A42F97C9368ECB9C3C1BD50B83EAC /* VideoScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC439C201F68AF1EFFAC34B79FC8AABD /* VideoScrollView.swift */; };
|
||||
1930B7608D16293CE006BBFFA59D2227 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3212113385A8FBBDB272BD23C409FF61 /* Foundation.framework */; };
|
||||
3128FA0C6EF67C366889FD25B20C26B7 /* PryntTrimmerView-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = D432584627C3896D39BF7C2FE358F7B1 /* PryntTrimmerView-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
425982410476D4F38E665418296EA801 /* PryntTrimmerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F96BD1A00504790CD63CC5AAF94AAD4 /* PryntTrimmerView.swift */; };
|
||||
4B1654EAC59903A3F57D722DD9D63788 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3212113385A8FBBDB272BD23C409FF61 /* Foundation.framework */; };
|
||||
71138F4FB33001A655E478C0D395323E /* Pods-SimpleVideoAnimation-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 010BFB1D34029B68E2B40E8D668E3A8C /* Pods-SimpleVideoAnimation-dummy.m */; };
|
||||
79AC849669741DC41B762F02D976F3B3 /* VideoCropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8625023B3E6DF67C7E31E4320747783 /* VideoCropView.swift */; };
|
||||
8FE524682461A32D247F3280857E8576 /* ThumbSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E3490BF84958387765BD324D9462ADE /* ThumbSelectorView.swift */; };
|
||||
A1C503843AC95229CA9962770191D8CB /* AVAssetTimeSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = F60A650D855F62F330AA252B6C596BBB /* AVAssetTimeSelector.swift */; };
|
||||
B47B377106D48A971F34C95FD6027D4E /* HandlerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3DD5E1779CA8E74EA322A3C642F0050 /* HandlerView.swift */; };
|
||||
DD7151461459B7C1560131156E029E30 /* Pods-SimpleVideoAnimation-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = BCA28EAFECF92C9585C9095F6F893A77 /* Pods-SimpleVideoAnimation-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
E6F4D1AA87BE2E09EFB06D3A7DB61858 /* PryntTrimmerView-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 1275509E869B124B7D2117626286EE7E /* PryntTrimmerView-dummy.m */; };
|
||||
EAB22516ACAA6EA3DA06A1BE26196675 /* CropMaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5574049F3812F6B26ABE1805FD9F4C18 /* CropMaskView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
86668A496B743E07BBAD7FF5F1AE251A /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 492A3F539D36426FFCED62F4F58F688B;
|
||||
remoteInfo = PryntTrimmerView;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0038897C32633F5F73BE1AEAC19DE309 /* Pods-SimpleVideoAnimation.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-SimpleVideoAnimation.modulemap"; sourceTree = "<group>"; };
|
||||
010BFB1D34029B68E2B40E8D668E3A8C /* Pods-SimpleVideoAnimation-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-SimpleVideoAnimation-dummy.m"; sourceTree = "<group>"; };
|
||||
017DD82CAC46C38F8A97838282A19238 /* Pods_SimpleVideoAnimation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_SimpleVideoAnimation.framework; path = "Pods-SimpleVideoAnimation.framework"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0C3506BDF9400573529648DF3D801256 /* Pods-SimpleVideoAnimation.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SimpleVideoAnimation.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
0F96BD1A00504790CD63CC5AAF94AAD4 /* PryntTrimmerView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PryntTrimmerView.swift; path = PryntTrimmerView/Classes/Trimmer/PryntTrimmerView.swift; sourceTree = "<group>"; };
|
||||
1275509E869B124B7D2117626286EE7E /* PryntTrimmerView-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "PryntTrimmerView-dummy.m"; sourceTree = "<group>"; };
|
||||
2A4A1BACF84E64074643AF16313BED24 /* AssetVideoScrollView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssetVideoScrollView.swift; path = PryntTrimmerView/Classes/Parents/AssetVideoScrollView.swift; sourceTree = "<group>"; };
|
||||
3212113385A8FBBDB272BD23C409FF61 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||
44D06F46436AACB33E8D2E85504271CE /* Pods-SimpleVideoAnimation-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-SimpleVideoAnimation-acknowledgements.markdown"; sourceTree = "<group>"; };
|
||||
4F75A400052C49C7C0424C6199354326 /* Pods-SimpleVideoAnimation-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-SimpleVideoAnimation-Info.plist"; sourceTree = "<group>"; };
|
||||
5574049F3812F6B26ABE1805FD9F4C18 /* CropMaskView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CropMaskView.swift; path = PryntTrimmerView/Classes/Cropper/CropMaskView.swift; sourceTree = "<group>"; };
|
||||
56395D8727605D379FEE467729B17928 /* PryntTrimmerView.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = PryntTrimmerView.xcconfig; sourceTree = "<group>"; };
|
||||
782DD95E3C1D9DD6570A9B32B5E8C027 /* PryntTrimmerView.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = PryntTrimmerView.modulemap; sourceTree = "<group>"; };
|
||||
9541F8AE153E6A07FF82F8BE4D1DF4CE /* Pods-SimpleVideoAnimation-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SimpleVideoAnimation-frameworks.sh"; sourceTree = "<group>"; };
|
||||
9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
|
||||
9E3490BF84958387765BD324D9462ADE /* ThumbSelectorView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ThumbSelectorView.swift; path = PryntTrimmerView/Classes/ThumbSelectorView.swift; sourceTree = "<group>"; };
|
||||
A21A3E5DAE9D963771CB82663DB77372 /* PryntTrimmerView-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "PryntTrimmerView-prefix.pch"; sourceTree = "<group>"; };
|
||||
BCA28EAFECF92C9585C9095F6F893A77 /* Pods-SimpleVideoAnimation-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-SimpleVideoAnimation-umbrella.h"; sourceTree = "<group>"; };
|
||||
C45D1BDF6010C3F370037BA1366D39A9 /* Pods-SimpleVideoAnimation.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SimpleVideoAnimation.release.xcconfig"; sourceTree = "<group>"; };
|
||||
C63296516747E930E004498D034CBEC8 /* PryntTrimmerView-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "PryntTrimmerView-Info.plist"; sourceTree = "<group>"; };
|
||||
CB21E0A22EAC99D55621CBFDCE99B6FB /* Pods-SimpleVideoAnimation-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-SimpleVideoAnimation-acknowledgements.plist"; sourceTree = "<group>"; };
|
||||
CC439C201F68AF1EFFAC34B79FC8AABD /* VideoScrollView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = VideoScrollView.swift; path = PryntTrimmerView/Classes/Cropper/VideoScrollView.swift; sourceTree = "<group>"; };
|
||||
D09DB48D8145BFA029AAEA61818EC68B /* PryntTrimmerView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = PryntTrimmerView.framework; path = PryntTrimmerView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D432584627C3896D39BF7C2FE358F7B1 /* PryntTrimmerView-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "PryntTrimmerView-umbrella.h"; sourceTree = "<group>"; };
|
||||
E3DD5E1779CA8E74EA322A3C642F0050 /* HandlerView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HandlerView.swift; path = PryntTrimmerView/Classes/Trimmer/HandlerView.swift; sourceTree = "<group>"; };
|
||||
F60A650D855F62F330AA252B6C596BBB /* AVAssetTimeSelector.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AVAssetTimeSelector.swift; path = PryntTrimmerView/Classes/Parents/AVAssetTimeSelector.swift; sourceTree = "<group>"; };
|
||||
F8625023B3E6DF67C7E31E4320747783 /* VideoCropView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = VideoCropView.swift; path = PryntTrimmerView/Classes/Cropper/VideoCropView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
AF1BE2436FA9C0AFA75A7FA0ADD09D5B /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1930B7608D16293CE006BBFFA59D2227 /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
C96EDBA35CA5052A71FDC4046C1C72B2 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4B1654EAC59903A3F57D722DD9D63788 /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
3299D943353D836EC70C7C067763BC0A /* PryntTrimmerView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2A4A1BACF84E64074643AF16313BED24 /* AssetVideoScrollView.swift */,
|
||||
F60A650D855F62F330AA252B6C596BBB /* AVAssetTimeSelector.swift */,
|
||||
5574049F3812F6B26ABE1805FD9F4C18 /* CropMaskView.swift */,
|
||||
E3DD5E1779CA8E74EA322A3C642F0050 /* HandlerView.swift */,
|
||||
0F96BD1A00504790CD63CC5AAF94AAD4 /* PryntTrimmerView.swift */,
|
||||
9E3490BF84958387765BD324D9462ADE /* ThumbSelectorView.swift */,
|
||||
F8625023B3E6DF67C7E31E4320747783 /* VideoCropView.swift */,
|
||||
CC439C201F68AF1EFFAC34B79FC8AABD /* VideoScrollView.swift */,
|
||||
913459968288E6874F51EBA6C6BC4F42 /* Support Files */,
|
||||
);
|
||||
name = PryntTrimmerView;
|
||||
path = PryntTrimmerView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5F0F96C3DFA71EC2D7EDB6D805C616DF /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
017DD82CAC46C38F8A97838282A19238 /* Pods_SimpleVideoAnimation.framework */,
|
||||
D09DB48D8145BFA029AAEA61818EC68B /* PryntTrimmerView.framework */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
657908C41F07AE316229BADE15AE40AA /* Targets Support Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
68CC947BC3E006F2F0C7914F735374B2 /* Pods-SimpleVideoAnimation */,
|
||||
);
|
||||
name = "Targets Support Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
68CC947BC3E006F2F0C7914F735374B2 /* Pods-SimpleVideoAnimation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0038897C32633F5F73BE1AEAC19DE309 /* Pods-SimpleVideoAnimation.modulemap */,
|
||||
44D06F46436AACB33E8D2E85504271CE /* Pods-SimpleVideoAnimation-acknowledgements.markdown */,
|
||||
CB21E0A22EAC99D55621CBFDCE99B6FB /* Pods-SimpleVideoAnimation-acknowledgements.plist */,
|
||||
010BFB1D34029B68E2B40E8D668E3A8C /* Pods-SimpleVideoAnimation-dummy.m */,
|
||||
9541F8AE153E6A07FF82F8BE4D1DF4CE /* Pods-SimpleVideoAnimation-frameworks.sh */,
|
||||
4F75A400052C49C7C0424C6199354326 /* Pods-SimpleVideoAnimation-Info.plist */,
|
||||
BCA28EAFECF92C9585C9095F6F893A77 /* Pods-SimpleVideoAnimation-umbrella.h */,
|
||||
0C3506BDF9400573529648DF3D801256 /* Pods-SimpleVideoAnimation.debug.xcconfig */,
|
||||
C45D1BDF6010C3F370037BA1366D39A9 /* Pods-SimpleVideoAnimation.release.xcconfig */,
|
||||
);
|
||||
name = "Pods-SimpleVideoAnimation";
|
||||
path = "Target Support Files/Pods-SimpleVideoAnimation";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
913459968288E6874F51EBA6C6BC4F42 /* Support Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
782DD95E3C1D9DD6570A9B32B5E8C027 /* PryntTrimmerView.modulemap */,
|
||||
56395D8727605D379FEE467729B17928 /* PryntTrimmerView.xcconfig */,
|
||||
1275509E869B124B7D2117626286EE7E /* PryntTrimmerView-dummy.m */,
|
||||
C63296516747E930E004498D034CBEC8 /* PryntTrimmerView-Info.plist */,
|
||||
A21A3E5DAE9D963771CB82663DB77372 /* PryntTrimmerView-prefix.pch */,
|
||||
D432584627C3896D39BF7C2FE358F7B1 /* PryntTrimmerView-umbrella.h */,
|
||||
);
|
||||
name = "Support Files";
|
||||
path = "../Target Support Files/PryntTrimmerView";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9D4B4E6D53A1F0FBAE805F589D033B5A /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3299D943353D836EC70C7C067763BC0A /* PryntTrimmerView */,
|
||||
);
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C0834CEBB1379A84116EF29F93051C60 /* iOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3212113385A8FBBDB272BD23C409FF61 /* Foundation.framework */,
|
||||
);
|
||||
name = iOS;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CF1408CF629C7361332E53B88F7BD30C = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9D940727FF8FB9C785EB98E56350EF41 /* Podfile */,
|
||||
D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */,
|
||||
9D4B4E6D53A1F0FBAE805F589D033B5A /* Pods */,
|
||||
5F0F96C3DFA71EC2D7EDB6D805C616DF /* Products */,
|
||||
657908C41F07AE316229BADE15AE40AA /* Targets Support Files */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C0834CEBB1379A84116EF29F93051C60 /* iOS */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
86A339BA1B223A27BCFB984A3C47E28B /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3128FA0C6EF67C366889FD25B20C26B7 /* PryntTrimmerView-umbrella.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DFB6EC49F1D54FFAAF97A8DC14107147 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DD7151461459B7C1560131156E029E30 /* Pods-SimpleVideoAnimation-umbrella.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
492A3F539D36426FFCED62F4F58F688B /* PryntTrimmerView */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 5F256E2E76CAD307A29C8FD48E4430F4 /* Build configuration list for PBXNativeTarget "PryntTrimmerView" */;
|
||||
buildPhases = (
|
||||
86A339BA1B223A27BCFB984A3C47E28B /* Headers */,
|
||||
29AFF14336C5C60CF4D7ECE4FCF71B99 /* Sources */,
|
||||
AF1BE2436FA9C0AFA75A7FA0ADD09D5B /* Frameworks */,
|
||||
90642AE968639B75967B5E9FBE4FB582 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = PryntTrimmerView;
|
||||
productName = PryntTrimmerView;
|
||||
productReference = D09DB48D8145BFA029AAEA61818EC68B /* PryntTrimmerView.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
A385EDF46896AAD667E7CB6AEBEEA453 /* Pods-SimpleVideoAnimation */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 5510412BFB33A41088328A2944E8429E /* Build configuration list for PBXNativeTarget "Pods-SimpleVideoAnimation" */;
|
||||
buildPhases = (
|
||||
DFB6EC49F1D54FFAAF97A8DC14107147 /* Headers */,
|
||||
47130975D92528825F7CD9F43190F66E /* Sources */,
|
||||
C96EDBA35CA5052A71FDC4046C1C72B2 /* Frameworks */,
|
||||
90C5D17977835042DD034C1A0D848044 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
4F0D8E61E9CE9580D4CB4657DFBBF101 /* PBXTargetDependency */,
|
||||
);
|
||||
name = "Pods-SimpleVideoAnimation";
|
||||
productName = "Pods-SimpleVideoAnimation";
|
||||
productReference = 017DD82CAC46C38F8A97838282A19238 /* Pods_SimpleVideoAnimation.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
BFDFE7DC352907FC980B868725387E98 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1100;
|
||||
LastUpgradeCheck = 1100;
|
||||
};
|
||||
buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = CF1408CF629C7361332E53B88F7BD30C;
|
||||
productRefGroup = 5F0F96C3DFA71EC2D7EDB6D805C616DF /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
A385EDF46896AAD667E7CB6AEBEEA453 /* Pods-SimpleVideoAnimation */,
|
||||
492A3F539D36426FFCED62F4F58F688B /* PryntTrimmerView */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
90642AE968639B75967B5E9FBE4FB582 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
90C5D17977835042DD034C1A0D848044 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
29AFF14336C5C60CF4D7ECE4FCF71B99 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
032F877127CB819B89EEF1CD59F35190 /* AssetVideoScrollView.swift in Sources */,
|
||||
A1C503843AC95229CA9962770191D8CB /* AVAssetTimeSelector.swift in Sources */,
|
||||
EAB22516ACAA6EA3DA06A1BE26196675 /* CropMaskView.swift in Sources */,
|
||||
B47B377106D48A971F34C95FD6027D4E /* HandlerView.swift in Sources */,
|
||||
E6F4D1AA87BE2E09EFB06D3A7DB61858 /* PryntTrimmerView-dummy.m in Sources */,
|
||||
425982410476D4F38E665418296EA801 /* PryntTrimmerView.swift in Sources */,
|
||||
8FE524682461A32D247F3280857E8576 /* ThumbSelectorView.swift in Sources */,
|
||||
79AC849669741DC41B762F02D976F3B3 /* VideoCropView.swift in Sources */,
|
||||
084A42F97C9368ECB9C3C1BD50B83EAC /* VideoScrollView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
47130975D92528825F7CD9F43190F66E /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
71138F4FB33001A655E478C0D395323E /* Pods-SimpleVideoAnimation-dummy.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
4F0D8E61E9CE9580D4CB4657DFBBF101 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = PryntTrimmerView;
|
||||
target = 492A3F539D36426FFCED62F4F58F688B /* PryntTrimmerView */;
|
||||
targetProxy = 86668A496B743E07BBAD7FF5F1AE251A /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
13967C5C8F4B10B3EE4D6C208A0A7BEB /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"POD_CONFIGURATION_DEBUG=1",
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.6;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRIP_INSTALLED_PRODUCT = NO;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
SYMROOT = "${SRCROOT}/../build";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
22EFBC31083FBA35442432B8942CCB05 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = C45D1BDF6010C3F370037BA1366D39A9 /* Pods-SimpleVideoAnimation.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
|
||||
CLANG_ENABLE_OBJC_WEAK = NO;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = "Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
MODULEMAP_FILE = "Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.modulemap";
|
||||
OTHER_LDFLAGS = "";
|
||||
OTHER_LIBTOOLFLAGS = "";
|
||||
PODS_ROOT = "$(SRCROOT)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
4812C7722161295CE314C28F40961E87 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 56395D8727605D379FEE467729B17928 /* PryntTrimmerView.xcconfig */;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
GCC_PREFIX_HEADER = "Target Support Files/PryntTrimmerView/PryntTrimmerView-prefix.pch";
|
||||
INFOPLIST_FILE = "Target Support Files/PryntTrimmerView/PryntTrimmerView-Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MODULEMAP_FILE = "Target Support Files/PryntTrimmerView/PryntTrimmerView.modulemap";
|
||||
PRODUCT_MODULE_NAME = PryntTrimmerView;
|
||||
PRODUCT_NAME = PryntTrimmerView;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
72B8A024B2332A85E8D4B741B60F6372 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 0C3506BDF9400573529648DF3D801256 /* Pods-SimpleVideoAnimation.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
|
||||
CLANG_ENABLE_OBJC_WEAK = NO;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = "Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
MODULEMAP_FILE = "Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.modulemap";
|
||||
OTHER_LDFLAGS = "";
|
||||
OTHER_LIBTOOLFLAGS = "";
|
||||
PODS_ROOT = "$(SRCROOT)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
871B6E28ABA34DAE69824AB9D22D3B7D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"POD_CONFIGURATION_RELEASE=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.6;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRIP_INSTALLED_PRODUCT = NO;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
SYMROOT = "${SRCROOT}/../build";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F1B17B29AA8479A18A091848A6D12474 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 56395D8727605D379FEE467729B17928 /* PryntTrimmerView.xcconfig */;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
GCC_PREFIX_HEADER = "Target Support Files/PryntTrimmerView/PryntTrimmerView-prefix.pch";
|
||||
INFOPLIST_FILE = "Target Support Files/PryntTrimmerView/PryntTrimmerView-Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MODULEMAP_FILE = "Target Support Files/PryntTrimmerView/PryntTrimmerView.modulemap";
|
||||
PRODUCT_MODULE_NAME = PryntTrimmerView;
|
||||
PRODUCT_NAME = PryntTrimmerView;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
13967C5C8F4B10B3EE4D6C208A0A7BEB /* Debug */,
|
||||
871B6E28ABA34DAE69824AB9D22D3B7D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
5510412BFB33A41088328A2944E8429E /* Build configuration list for PBXNativeTarget "Pods-SimpleVideoAnimation" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
72B8A024B2332A85E8D4B741B60F6372 /* Debug */,
|
||||
22EFBC31083FBA35442432B8942CCB05 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
5F256E2E76CAD307A29C8FD48E4430F4 /* Build configuration list for PBXNativeTarget "PryntTrimmerView" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F1B17B29AA8479A18A091848A6D12474 /* Debug */,
|
||||
4812C7722161295CE314C28F40961E87 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Prynt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// CropMaskView.swift
|
||||
// PryntTrimmerView
|
||||
//
|
||||
// Created by Henry on 10/04/2017.
|
||||
// Copyright © 2017 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class CropMaskView: UIView {
|
||||
|
||||
let cropBoxView = UIView()
|
||||
let frameView = UIView()
|
||||
let maskLayer = CAShapeLayer()
|
||||
let frameLayer = CAShapeLayer()
|
||||
|
||||
private let lineWidth: CGFloat = 4.0
|
||||
private var cropFrame: CGRect = CGRect.zero
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
private func setupSubviews() {
|
||||
|
||||
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
|
||||
maskLayer.fillColor = UIColor.black.cgColor
|
||||
maskLayer.opacity = 1.0
|
||||
|
||||
frameLayer.strokeColor = UIColor.white.cgColor
|
||||
frameLayer.fillColor = UIColor.clear.cgColor
|
||||
|
||||
frameView.layer.addSublayer(frameLayer)
|
||||
cropBoxView.layer.mask = maskLayer
|
||||
|
||||
cropBoxView.translatesAutoresizingMaskIntoConstraints = false
|
||||
cropBoxView.backgroundColor = UIColor.white.withAlphaComponent(0.7)
|
||||
addSubview(cropBoxView)
|
||||
addSubview(frameView)
|
||||
|
||||
cropBoxView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
|
||||
cropBoxView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
|
||||
cropBoxView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
cropBoxView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let path = UIBezierPath(rect: bounds)
|
||||
let framePath = UIBezierPath(rect: cropFrame)
|
||||
path.append(framePath)
|
||||
path.usesEvenOddFillRule = true
|
||||
maskLayer.path = path.cgPath
|
||||
|
||||
framePath.lineWidth = lineWidth
|
||||
frameLayer.path = framePath.cgPath
|
||||
}
|
||||
|
||||
func setCropFrame(_ frame: CGRect, animated: Bool) {
|
||||
|
||||
cropFrame = frame
|
||||
guard animated else {
|
||||
setNeedsLayout()
|
||||
return
|
||||
}
|
||||
|
||||
let (path, framePath) = getPaths(with: cropFrame)
|
||||
|
||||
CATransaction.begin()
|
||||
|
||||
let animation = getPathAnimation(with: path)
|
||||
maskLayer.path = maskLayer.presentation()?.path
|
||||
frameLayer.path = frameLayer.presentation()?.path
|
||||
|
||||
maskLayer.removeAnimation(forKey: "maskPath")
|
||||
maskLayer.add(animation, forKey: "maskPath")
|
||||
|
||||
animation.toValue = framePath
|
||||
frameLayer.removeAnimation(forKey: "framePath")
|
||||
frameLayer.add(animation, forKey: "framePath")
|
||||
CATransaction.commit()
|
||||
}
|
||||
|
||||
private func getPaths(with cropFrame: CGRect) -> (path: CGPath, framePath: CGPath) {
|
||||
|
||||
let path = UIBezierPath(rect: bounds)
|
||||
let framePath = UIBezierPath(rect: cropFrame)
|
||||
framePath.lineWidth = lineWidth
|
||||
path.append(framePath)
|
||||
path.usesEvenOddFillRule = true
|
||||
|
||||
return (path.cgPath, framePath.cgPath)
|
||||
}
|
||||
|
||||
private func getPathAnimation(with path: CGPath) -> CABasicAnimation {
|
||||
|
||||
let animation = CABasicAnimation(keyPath: "path")
|
||||
animation.toValue = path
|
||||
animation.duration = 0.3
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
animation.fillMode = CAMediaTimingFillMode.both
|
||||
animation.isRemovedOnCompletion = false
|
||||
|
||||
return animation
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// VideoCropView.swift
|
||||
// PryntTrimmerView
|
||||
//
|
||||
// Created by Henry on 07/04/2017.
|
||||
// Copyright © 2017 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
private let margin: CGFloat = 16
|
||||
|
||||
/// A view to preview a video inside an `AVPlayer`, with a scroll view to be able to select a specific area of the video.
|
||||
/// Simply set the `asset` property to load you video inside the view, and use the `getImageCropFrame` method to retrieve the
|
||||
/// selected frame of the video.
|
||||
public class VideoCropView: UIView {
|
||||
|
||||
let videoScrollView = VideoScrollView()
|
||||
let cropMaskView = CropMaskView()
|
||||
|
||||
/// The asset being cropped. Setting the property will load the asset in the `AVPlayer` contained in the scroll view.
|
||||
public var asset: AVAsset? {
|
||||
didSet {
|
||||
if let asset = asset {
|
||||
videoScrollView.setupVideo(with: asset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cropFrame = CGRect.zero
|
||||
|
||||
/// The current aspect ratio of the crop view.
|
||||
public private(set) var aspectRatio = CGSize(width: 1, height: 1)
|
||||
|
||||
/// The player used in the scroll view. Use it if you want to seek a specific time of your video.
|
||||
public var player: AVPlayer? {
|
||||
return videoScrollView.player
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
private func setupSubviews() {
|
||||
|
||||
videoScrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(videoScrollView)
|
||||
|
||||
videoScrollView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
|
||||
videoScrollView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
|
||||
videoScrollView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
videoScrollView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
|
||||
cropMaskView.isUserInteractionEnabled = false
|
||||
cropMaskView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
addSubview(cropMaskView)
|
||||
|
||||
cropMaskView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
|
||||
cropMaskView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
|
||||
cropMaskView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
cropMaskView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
|
||||
setAspectRatio(aspectRatio, animated: false)
|
||||
}
|
||||
|
||||
/// Set the aspect ratio for the cropping box.
|
||||
///
|
||||
/// [Issue #3]: https://github.com/HHK1/PryntTrimmerView/issues/3
|
||||
/// - Parameters:
|
||||
/// - aspectRatio: The desired aspect ratio.
|
||||
/// - animated: true if you want to animate the change. TODO: the animation glitches sometimes. See [Issue #3]
|
||||
public func setAspectRatio(_ aspectRatio: CGSize, animated: Bool) {
|
||||
|
||||
self.aspectRatio = aspectRatio
|
||||
let ratio = aspectRatio.width / aspectRatio.height
|
||||
let cropBoxWidth = ratio > 1 ? (bounds.width - 2 * margin) : (bounds.height - 2 * margin) * ratio
|
||||
let cropBoxHeight = cropBoxWidth / ratio
|
||||
let origin = CGPoint(x: (bounds.width - cropBoxWidth) / 2, y: (bounds.height - cropBoxHeight) / 2)
|
||||
cropFrame = CGRect(origin: origin, size: CGSize(width: cropBoxWidth, height: cropBoxHeight))
|
||||
|
||||
let edgeInsets = UIEdgeInsets(top: origin.y, left: origin.x, bottom: origin.y, right: origin.x)
|
||||
let duration: TimeInterval = animated ? 0.15 : 0.0
|
||||
|
||||
cropMaskView.setCropFrame(cropFrame, animated: animated)
|
||||
UIView.animate(withDuration: duration, delay: 0, options: [.beginFromCurrentState, .curveEaseInOut], animations: {
|
||||
self.videoScrollView.scrollView.contentInset = edgeInsets
|
||||
}, completion: nil)
|
||||
videoScrollView.setZoomScaleAndCenter(animated: animated)
|
||||
}
|
||||
|
||||
/// Get the currently selected frame of the asset. You can then use it to perform the actual cropping of the video or of an
|
||||
/// isolated image
|
||||
public func getImageCropFrame() -> CGRect {
|
||||
|
||||
let imageSize = videoScrollView.assetSize
|
||||
let contentSize = videoScrollView.scrollView.contentSize
|
||||
let cropBoxFrame = cropFrame
|
||||
let contentOffset = videoScrollView.scrollView.contentOffset
|
||||
let edgeInsets = videoScrollView.scrollView.contentInset
|
||||
|
||||
var frame = CGRect.zero
|
||||
frame.origin.x = floor((contentOffset.x + edgeInsets.left) * (imageSize.width / contentSize.width))
|
||||
frame.origin.x = max(0, frame.origin.x)
|
||||
|
||||
frame.origin.y = floor((contentOffset.y + edgeInsets.top) * (imageSize.height / contentSize.height))
|
||||
frame.origin.y = max(0, frame.origin.y)
|
||||
|
||||
frame.size.width = ceil(cropBoxFrame.size.width * (imageSize.width / contentSize.width))
|
||||
frame.size.width = min(imageSize.width, frame.size.width)
|
||||
|
||||
frame.size.height = ceil(cropBoxFrame.size.height * (imageSize.height / contentSize.height))
|
||||
frame.size.height = min(imageSize.height, frame.size.height)
|
||||
return frame
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// VideoScrollView.swift
|
||||
// PryntTrimmerView
|
||||
//
|
||||
// Created by Henry on 10/04/2017.
|
||||
// Copyright © 2017 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
class VideoScrollView: UIView {
|
||||
|
||||
let scrollView = UIScrollView()
|
||||
var contentView = UIView()
|
||||
var assetSize = CGSize.zero
|
||||
|
||||
var playerItem: AVPlayerItem?
|
||||
var player: AVPlayer?
|
||||
var playerLayer: AVPlayerLayer?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
private func setupSubviews() {
|
||||
|
||||
scrollView.showsVerticalScrollIndicator = false
|
||||
scrollView.showsHorizontalScrollIndicator = false
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.addSubview(contentView)
|
||||
scrollView.delegate = self
|
||||
addSubview(scrollView)
|
||||
|
||||
scrollView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
|
||||
scrollView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
|
||||
scrollView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
scrollView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
}
|
||||
|
||||
func setupVideo(with asset: AVAsset) {
|
||||
|
||||
guard let track = asset.tracks(withMediaType: AVMediaType.video).first else { return }
|
||||
let trackSize = track.naturalSize.applying(track.preferredTransform)
|
||||
assetSize = CGSize(width: abs(trackSize.width), height: abs(trackSize.height))
|
||||
|
||||
scrollView.zoomScale = 1.0 // Reset zoom scale before changing the frame of the content view.
|
||||
playerItem = AVPlayerItem(asset: asset)
|
||||
let playerFrame = CGRect(x: 0, y: 0, width: assetSize.width, height: assetSize.height)
|
||||
addVideoLayer(with: playerFrame)
|
||||
|
||||
scrollView.contentSize = assetSize
|
||||
setZoomScaleAndCenter(animated: false)
|
||||
}
|
||||
|
||||
private func addVideoLayer(with playerFrame: CGRect) {
|
||||
|
||||
playerLayer?.removeFromSuperlayer()
|
||||
player = AVPlayer(playerItem: playerItem)
|
||||
playerLayer = AVPlayerLayer(player: player)
|
||||
playerLayer?.frame = playerFrame
|
||||
playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||
|
||||
contentView.frame = playerFrame
|
||||
contentView.layer.addSublayer(playerLayer!)
|
||||
}
|
||||
|
||||
func setZoomScaleAndCenter(animated: Bool) {
|
||||
|
||||
guard assetSize != CGSize.zero else { return }
|
||||
|
||||
let scrollWidth = scrollView.bounds.width - scrollView.contentInset.left - scrollView.contentInset.right
|
||||
let scrollHeight = scrollView.bounds.height - scrollView.contentInset.top - scrollView.contentInset.bottom
|
||||
let scale = max(scrollWidth / assetSize.width, scrollHeight / assetSize.height)
|
||||
scrollView.minimumZoomScale = scale
|
||||
scrollView.maximumZoomScale = 3.0
|
||||
|
||||
var offset = scrollView.contentOffset
|
||||
offset.x = -scrollView.contentInset.left - (scrollWidth - assetSize.width * scale) / 2
|
||||
offset.y = -scrollView.contentInset.top - (scrollHeight - assetSize.height * scale) / 2
|
||||
|
||||
scrollView.setZoomScale(scale, animated: animated)
|
||||
scrollView.setContentOffset(offset, animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
extension VideoScrollView: UIScrollViewDelegate {
|
||||
|
||||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||
return contentView
|
||||
}
|
||||
|
||||
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
|
||||
let scaledAssetSize = CGSize(width: assetSize.width * scale, height: assetSize.height * scale)
|
||||
scrollView.contentSize = scaledAssetSize
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// AVAssetTimeSelector.swift
|
||||
// Pods
|
||||
//
|
||||
// Created by Henry on 06/04/2017.
|
||||
//
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
/// A generic class to display an asset into a scroll view with thumbnail images, and make the equivalence between a time in
|
||||
// the asset and a position in the scroll view
|
||||
public class AVAssetTimeSelector: UIView, UIScrollViewDelegate {
|
||||
|
||||
let assetPreview = AssetVideoScrollView()
|
||||
|
||||
/// The asset to be displayed in the underlying scroll view. Setting a new asset will automatically refresh the thumbnails.
|
||||
public var asset: AVAsset? {
|
||||
didSet {
|
||||
assetDidChange(newAsset: asset)
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
func setupSubviews() {
|
||||
setupAssetPreview()
|
||||
constrainAssetPreview()
|
||||
}
|
||||
|
||||
// MARK: - Asset Preview
|
||||
|
||||
func setupAssetPreview() {
|
||||
|
||||
assetPreview.translatesAutoresizingMaskIntoConstraints = false
|
||||
assetPreview.delegate = self
|
||||
addSubview(assetPreview)
|
||||
}
|
||||
|
||||
func constrainAssetPreview() {
|
||||
assetPreview.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
|
||||
assetPreview.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
|
||||
assetPreview.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
assetPreview.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
}
|
||||
|
||||
func assetDidChange(newAsset: AVAsset?) {
|
||||
if let asset = newAsset {
|
||||
assetPreview.regenerateThumbnails(for: asset)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Time & Position Equivalence
|
||||
|
||||
var durationSize: CGFloat {
|
||||
return assetPreview.contentSize.width
|
||||
}
|
||||
|
||||
func getTime(from position: CGFloat) -> CMTime? {
|
||||
guard let asset = asset else {
|
||||
return nil
|
||||
}
|
||||
let normalizedRatio = max(min(1, position / durationSize), 0)
|
||||
let positionTimeValue = Double(normalizedRatio) * Double(asset.duration.value)
|
||||
return CMTime(value: Int64(positionTimeValue), timescale: asset.duration.timescale)
|
||||
}
|
||||
|
||||
func getPosition(from time: CMTime) -> CGFloat? {
|
||||
guard let asset = asset else {
|
||||
return nil
|
||||
}
|
||||
let timeRatio = CGFloat(time.value) * CGFloat(asset.duration.timescale) /
|
||||
(CGFloat(time.timescale) * CGFloat(asset.duration.value))
|
||||
return timeRatio * durationSize
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// AssetVideoScrollView.swift
|
||||
// PryntTrimmerView
|
||||
//
|
||||
// Created by HHK on 28/03/2017.
|
||||
// Copyright © 2017 Prynt. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
import UIKit
|
||||
|
||||
class AssetVideoScrollView: UIScrollView {
|
||||
|
||||
private var widthConstraint: NSLayoutConstraint?
|
||||
|
||||
let contentView = UIView()
|
||||
var maxDuration: Double = 15
|
||||
private var generator: AVAssetImageGenerator?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
setupSubviews()
|
||||
}
|
||||
|
||||
private func setupSubviews() {
|
||||
|
||||
backgroundColor = .clear
|
||||
showsVerticalScrollIndicator = false
|
||||
showsHorizontalScrollIndicator = false
|
||||
clipsToBounds = true
|
||||
|
||||
contentView.backgroundColor = .clear
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.tag = -1
|
||||
addSubview(contentView)
|
||||
|
||||
contentView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
|
||||
contentView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
contentView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
widthConstraint = contentView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.0)
|
||||
widthConstraint?.isActive = true
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
contentSize = contentView.bounds.size
|
||||
}
|
||||
|
||||
internal func regenerateThumbnails(for asset: AVAsset) {
|
||||
guard let thumbnailSize = getThumbnailFrameSize(from: asset), thumbnailSize.width != 0 else {
|
||||
print("Could not calculate the thumbnail size.")
|
||||
return
|
||||
}
|
||||
|
||||
generator?.cancelAllCGImageGeneration()
|
||||
removeFormerThumbnails()
|
||||
let newContentSize = setContentSize(for: asset)
|
||||
let visibleThumbnailsCount = Int(ceil(frame.width / thumbnailSize.width))
|
||||
let thumbnailCount = Int(ceil(newContentSize.width / thumbnailSize.width))
|
||||
addThumbnailViews(thumbnailCount, size: thumbnailSize)
|
||||
let timesForThumbnail = getThumbnailTimes(for: asset, numberOfThumbnails: thumbnailCount)
|
||||
generateImages(for: asset, at: timesForThumbnail, with: thumbnailSize, visibleThumnails: visibleThumbnailsCount)
|
||||
}
|
||||
|
||||
private func getThumbnailFrameSize(from asset: AVAsset) -> CGSize? {
|
||||
guard let track = asset.tracks(withMediaType: AVMediaType.video).first else { return nil}
|
||||
|
||||
let assetSize = track.naturalSize.applying(track.preferredTransform)
|
||||
|
||||
let height = frame.height
|
||||
let ratio = assetSize.width / assetSize.height
|
||||
let width = height * ratio
|
||||
return CGSize(width: abs(width), height: abs(height))
|
||||
}
|
||||
|
||||
private func removeFormerThumbnails() {
|
||||
contentView.subviews.forEach({ $0.removeFromSuperview() })
|
||||
}
|
||||
|
||||
private func setContentSize(for asset: AVAsset) -> CGSize {
|
||||
|
||||
let contentWidthFactor = CGFloat(max(1, asset.duration.seconds / maxDuration))
|
||||
widthConstraint?.isActive = false
|
||||
widthConstraint = contentView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: contentWidthFactor)
|
||||
widthConstraint?.isActive = true
|
||||
layoutIfNeeded()
|
||||
return contentView.bounds.size
|
||||
}
|
||||
|
||||
private func addThumbnailViews(_ count: Int, size: CGSize) {
|
||||
|
||||
for index in 0..<count {
|
||||
|
||||
let thumbnailView = UIImageView(frame: CGRect.zero)
|
||||
thumbnailView.clipsToBounds = true
|
||||
|
||||
let viewEndX = CGFloat(index) * size.width + size.width
|
||||
|
||||
if viewEndX > contentView.frame.width {
|
||||
thumbnailView.frame.size = CGSize(width: size.width + (contentView.frame.width - viewEndX), height: size.height)
|
||||
thumbnailView.contentMode = .scaleAspectFill
|
||||
} else {
|
||||
thumbnailView.frame.size = size
|
||||
thumbnailView.contentMode = .scaleAspectFit
|
||||
}
|
||||
|
||||
thumbnailView.frame.origin = CGPoint(x: CGFloat(index) * size.width, y: 0)
|
||||
thumbnailView.tag = index
|
||||
contentView.addSubview(thumbnailView)
|
||||
}
|
||||
}
|
||||
|
||||
private func getThumbnailTimes(for asset: AVAsset, numberOfThumbnails: Int) -> [NSValue] {
|
||||
|
||||
let timeIncrement = (asset.duration.seconds * 1000) / Double(numberOfThumbnails)
|
||||
var timesForThumbnails = [NSValue]()
|
||||
for index in 0..<numberOfThumbnails {
|
||||
let cmTime = CMTime(value: Int64(timeIncrement * Float64(index)), timescale: 1000)
|
||||
let nsValue = NSValue(time: cmTime)
|
||||
timesForThumbnails.append(nsValue)
|
||||
}
|
||||
return timesForThumbnails
|
||||
}
|
||||
|
||||
private func generateImages(for asset: AVAsset, at times: [NSValue], with maximumSize: CGSize, visibleThumnails: Int) {
|
||||
|
||||
generator = AVAssetImageGenerator(asset: asset)
|
||||
generator?.appliesPreferredTrackTransform = true
|
||||
let scaledSize = CGSize(width: maximumSize.width * UIScreen.main.scale, height: maximumSize.height * UIScreen.main.scale)
|
||||
generator?.maximumSize = scaledSize
|
||||
var count = 0
|
||||
|
||||
let handler: AVAssetImageGeneratorCompletionHandler = { [weak self] (_, cgimage, _, result, error) in
|
||||
if let cgimage = cgimage, error == nil && result == AVAssetImageGenerator.Result.succeeded {
|
||||
DispatchQueue.main.async(execute: { [weak self] () -> Void in
|
||||
|
||||
if count == 0 {
|
||||
self?.displayFirstImage(cgimage, visibleThumbnails: visibleThumnails)
|
||||
}
|
||||
self?.displayImage(cgimage, at: count)
|
||||
count += 1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
generator?.generateCGImagesAsynchronously(forTimes: times, completionHandler: handler)
|
||||
}
|
||||
|
||||
private func displayFirstImage(_ cgImage: CGImage, visibleThumbnails: Int) {
|
||||
for i in 0...visibleThumbnails {
|
||||
displayImage(cgImage, at: i)
|
||||
}
|
||||
}
|
||||
|
||||
private func displayImage(_ cgImage: CGImage, at index: Int) {
|
||||
if let imageView = contentView.viewWithTag(index) as? UIImageView {
|
||||
let uiimage = UIImage(cgImage: cgImage, scale: 1.0, orientation: UIImage.Orientation.up)
|
||||
imageView.image = uiimage
|
||||
}
|
||||
}
|
||||
}
|
||||
Examples/SimpleVideoAnimation/Pods/PryntTrimmerView/PryntTrimmerView/Classes/ThumbSelectorView.swift
Generated
+178
@@ -0,0 +1,178 @@
|
||||
//
|
||||
// ThumbSelectorView.swift
|
||||
// Pods
|
||||
//
|
||||
// Created by Henry on 06/04/2017.
|
||||
//
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
/// A delegate to be notified of when the thumb position has changed. Useful to link an instance of the ThumbSelectorView to a
|
||||
/// video preview like an `AVPlayer`.
|
||||
public protocol ThumbSelectorViewDelegate: class {
|
||||
func didChangeThumbPosition(_ imageTime: CMTime)
|
||||
}
|
||||
|
||||
/// A view to select a specific time of an `AVAsset`. It is composed of an asset preview within a scroll view, and a thumb view
|
||||
/// to select a precise time of the video. Set the `asset` property to load the video, and use the `selectedTime` property to
|
||||
// retrieve the exact frame of the asset that was selected.
|
||||
public class ThumbSelectorView: AVAssetTimeSelector {
|
||||
|
||||
public var thumbBorderColor: UIColor = .white {
|
||||
didSet {
|
||||
thumbView.layer.borderColor = thumbBorderColor.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
private let thumbView = UIImageView()
|
||||
private let dimmingView = UIView()
|
||||
|
||||
private var leftThumbConstraint: NSLayoutConstraint?
|
||||
private var currentThumbConstraint: CGFloat = 0
|
||||
|
||||
private var generator: AVAssetImageGenerator?
|
||||
|
||||
public weak var delegate: ThumbSelectorViewDelegate?
|
||||
|
||||
// MARK: - View & constraints configurations
|
||||
|
||||
override func setupSubviews() {
|
||||
super.setupSubviews()
|
||||
setupDimmingView()
|
||||
setupThumbView()
|
||||
}
|
||||
|
||||
private func setupDimmingView() {
|
||||
|
||||
dimmingView.translatesAutoresizingMaskIntoConstraints = false
|
||||
dimmingView.isUserInteractionEnabled = false
|
||||
dimmingView.backgroundColor = UIColor.white.withAlphaComponent(0.7)
|
||||
addSubview(dimmingView)
|
||||
dimmingView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
|
||||
dimmingView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
|
||||
dimmingView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
dimmingView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
}
|
||||
|
||||
private func setupThumbView() {
|
||||
|
||||
thumbView.translatesAutoresizingMaskIntoConstraints = false
|
||||
thumbView.layer.borderWidth = 2.0
|
||||
thumbView.layer.borderColor = thumbBorderColor.cgColor
|
||||
thumbView.isUserInteractionEnabled = true
|
||||
thumbView.contentMode = .scaleAspectFill
|
||||
thumbView.clipsToBounds = true
|
||||
addSubview(thumbView)
|
||||
|
||||
leftThumbConstraint = thumbView.leftAnchor.constraint(equalTo: leftAnchor)
|
||||
leftThumbConstraint?.isActive = true
|
||||
thumbView.widthAnchor.constraint(equalTo: thumbView.heightAnchor).isActive = true
|
||||
thumbView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
|
||||
thumbView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
|
||||
|
||||
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(ThumbSelectorView.handlePanGesture(_:)))
|
||||
thumbView.addGestureRecognizer(panGestureRecognizer)
|
||||
}
|
||||
|
||||
// MARK: - Gesture handling
|
||||
|
||||
@objc func handlePanGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
|
||||
guard let superView = gestureRecognizer.view?.superview else { return }
|
||||
|
||||
switch gestureRecognizer.state {
|
||||
|
||||
case .began:
|
||||
currentThumbConstraint = leftThumbConstraint!.constant
|
||||
updateSelectedTime()
|
||||
case .changed:
|
||||
|
||||
let translation = gestureRecognizer.translation(in: superView)
|
||||
updateThumbConstraint(with: translation)
|
||||
layoutIfNeeded()
|
||||
updateSelectedTime()
|
||||
|
||||
case .cancelled, .ended, .failed:
|
||||
updateSelectedTime()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
private func updateThumbConstraint(with translation: CGPoint) {
|
||||
let maxConstraint = frame.width - thumbView.frame.width
|
||||
let newConstraint = min(max(0, currentThumbConstraint + translation.x), maxConstraint)
|
||||
leftThumbConstraint?.constant = newConstraint
|
||||
}
|
||||
|
||||
// MARK: - Thumbnail Generation
|
||||
|
||||
override func assetDidChange(newAsset: AVAsset?) {
|
||||
if let asset = newAsset {
|
||||
setupThumbnailGenerator(with: asset)
|
||||
leftThumbConstraint?.constant = 0
|
||||
updateSelectedTime()
|
||||
}
|
||||
super.assetDidChange(newAsset: newAsset)
|
||||
}
|
||||
|
||||
private func setupThumbnailGenerator(with asset: AVAsset) {
|
||||
generator = AVAssetImageGenerator(asset: asset)
|
||||
generator?.appliesPreferredTrackTransform = true
|
||||
generator?.requestedTimeToleranceAfter = CMTime.zero
|
||||
generator?.requestedTimeToleranceBefore = CMTime.zero
|
||||
generator?.maximumSize = getThumbnailFrameSize(from: asset) ?? CGSize.zero
|
||||
}
|
||||
|
||||
private func getThumbnailFrameSize(from asset: AVAsset) -> CGSize? {
|
||||
guard let track = asset.tracks(withMediaType: AVMediaType.video).first else { return nil}
|
||||
|
||||
let assetSize = track.naturalSize.applying(track.preferredTransform)
|
||||
|
||||
let maxDimension = max(assetSize.width, assetSize.height)
|
||||
let minDimension = min(assetSize.width, assetSize.height)
|
||||
let ratio = maxDimension / minDimension
|
||||
let side = thumbView.frame.height * ratio * UIScreen.main.scale
|
||||
return CGSize(width: side, height: side)
|
||||
}
|
||||
|
||||
private func generateThumbnailImage(for time: CMTime) {
|
||||
|
||||
generator?.generateCGImagesAsynchronously(forTimes: [time as NSValue],
|
||||
completionHandler: { (_, image, _, _, _) in
|
||||
guard let image = image else {
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.generator?.cancelAllCGImageGeneration()
|
||||
let uiimage = UIImage(cgImage: image)
|
||||
self.thumbView.image = uiimage
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - Time & Position Equivalence
|
||||
|
||||
override var durationSize: CGFloat {
|
||||
return assetPreview.contentSize.width - thumbView.frame.width
|
||||
}
|
||||
|
||||
/// The currently selected time of the asset.
|
||||
public var selectedTime: CMTime? {
|
||||
let thumbPosition = thumbView.center.x + assetPreview.contentOffset.x - (thumbView.frame.width / 2)
|
||||
return getTime(from: thumbPosition)
|
||||
}
|
||||
|
||||
private func updateSelectedTime() {
|
||||
if let selectedTime = selectedTime {
|
||||
delegate?.didChangeThumbPosition(selectedTime)
|
||||
generateThumbnailImage(for: selectedTime)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIScrollViewDelegate
|
||||
|
||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
updateSelectedTime()
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// HandlerView.swift
|
||||
// PryntTrimmerView
|
||||
//
|
||||
// Created by HHK on 27/03/2017.
|
||||
// Copyright © 2017 Prynt. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
class HandlerView: UIView {
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let hitFrame = bounds.insetBy(dx: -20, dy: -20)
|
||||
return hitFrame.contains(point) ? self : nil
|
||||
}
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
let hitFrame = bounds.insetBy(dx: -20, dy: -20)
|
||||
return hitFrame.contains(point)
|
||||
}
|
||||
}
|
||||
+345
@@ -0,0 +1,345 @@
|
||||
//
|
||||
// PryntTrimmerView.swift
|
||||
// PryntTrimmerView
|
||||
//
|
||||
// Created by HHK on 27/03/2017.
|
||||
// Copyright © 2017 Prynt. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
import UIKit
|
||||
|
||||
public protocol TrimmerViewDelegate: class {
|
||||
func didChangePositionBar(_ playerTime: CMTime)
|
||||
func positionBarStoppedMoving(_ playerTime: CMTime)
|
||||
}
|
||||
|
||||
/// A view to select a specific time range of a video. It consists of an asset preview with thumbnails inside a scroll view, two
|
||||
/// handles on the side to select the beginning and the end of the range, and a position bar to synchronize the control with a
|
||||
/// video preview, typically with an `AVPlayer`.
|
||||
/// Load the video by setting the `asset` property. Access the `startTime` and `endTime` of the view to get the selected time
|
||||
// range
|
||||
@IBDesignable public class TrimmerView: AVAssetTimeSelector {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Color Customization
|
||||
|
||||
/// The color of the main border of the view
|
||||
@IBInspectable public var mainColor: UIColor = UIColor.orange {
|
||||
didSet {
|
||||
updateMainColor()
|
||||
}
|
||||
}
|
||||
|
||||
/// The color of the handles on the side of the view
|
||||
@IBInspectable public var handleColor: UIColor = UIColor.gray {
|
||||
didSet {
|
||||
updateHandleColor()
|
||||
}
|
||||
}
|
||||
|
||||
/// The color of the position indicator
|
||||
@IBInspectable public var positionBarColor: UIColor = UIColor.white {
|
||||
didSet {
|
||||
positionBar.backgroundColor = positionBarColor
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Interface
|
||||
|
||||
public weak var delegate: TrimmerViewDelegate?
|
||||
|
||||
// MARK: Subviews
|
||||
|
||||
private let trimView = UIView()
|
||||
private let leftHandleView = HandlerView()
|
||||
private let rightHandleView = HandlerView()
|
||||
private let positionBar = UIView()
|
||||
private let leftHandleKnob = UIView()
|
||||
private let rightHandleKnob = UIView()
|
||||
private let leftMaskView = UIView()
|
||||
private let rightMaskView = UIView()
|
||||
|
||||
// MARK: Constraints
|
||||
|
||||
private var currentLeftConstraint: CGFloat = 0
|
||||
private var currentRightConstraint: CGFloat = 0
|
||||
private var leftConstraint: NSLayoutConstraint?
|
||||
private var rightConstraint: NSLayoutConstraint?
|
||||
private var positionConstraint: NSLayoutConstraint?
|
||||
|
||||
private let handleWidth: CGFloat = 15
|
||||
|
||||
/// The maximum duration allowed for the trimming. Change it before setting the asset, as the asset preview
|
||||
public var maxDuration: Double = 15 {
|
||||
didSet {
|
||||
assetPreview.maxDuration = maxDuration
|
||||
}
|
||||
}
|
||||
|
||||
/// The minimum duration allowed for the trimming. The handles won't pan further if the minimum duration is attained.
|
||||
public var minDuration: Double = 3
|
||||
|
||||
// MARK: - View & constraints configurations
|
||||
|
||||
override func setupSubviews() {
|
||||
|
||||
super.setupSubviews()
|
||||
backgroundColor = UIColor.clear
|
||||
layer.zPosition = 1
|
||||
setupTrimmerView()
|
||||
setupHandleView()
|
||||
setupMaskView()
|
||||
setupPositionBar()
|
||||
setupGestures()
|
||||
updateMainColor()
|
||||
updateHandleColor()
|
||||
}
|
||||
|
||||
override func constrainAssetPreview() {
|
||||
assetPreview.leftAnchor.constraint(equalTo: leftAnchor, constant: handleWidth).isActive = true
|
||||
assetPreview.rightAnchor.constraint(equalTo: rightAnchor, constant: -handleWidth).isActive = true
|
||||
assetPreview.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
assetPreview.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
}
|
||||
|
||||
private func setupTrimmerView() {
|
||||
|
||||
trimView.layer.borderWidth = 2.0
|
||||
trimView.layer.cornerRadius = 2.0
|
||||
trimView.translatesAutoresizingMaskIntoConstraints = false
|
||||
trimView.isUserInteractionEnabled = false
|
||||
addSubview(trimView)
|
||||
|
||||
trimView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
trimView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
leftConstraint = trimView.leftAnchor.constraint(equalTo: leftAnchor)
|
||||
rightConstraint = trimView.rightAnchor.constraint(equalTo: rightAnchor)
|
||||
leftConstraint?.isActive = true
|
||||
rightConstraint?.isActive = true
|
||||
}
|
||||
|
||||
private func setupHandleView() {
|
||||
|
||||
leftHandleView.isUserInteractionEnabled = true
|
||||
leftHandleView.layer.cornerRadius = 2.0
|
||||
leftHandleView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(leftHandleView)
|
||||
|
||||
leftHandleView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
|
||||
leftHandleView.widthAnchor.constraint(equalToConstant: handleWidth).isActive = true
|
||||
leftHandleView.leftAnchor.constraint(equalTo: trimView.leftAnchor).isActive = true
|
||||
leftHandleView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
|
||||
|
||||
leftHandleKnob.translatesAutoresizingMaskIntoConstraints = false
|
||||
leftHandleView.addSubview(leftHandleKnob)
|
||||
|
||||
leftHandleKnob.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5).isActive = true
|
||||
leftHandleKnob.widthAnchor.constraint(equalToConstant: 2).isActive = true
|
||||
leftHandleKnob.centerYAnchor.constraint(equalTo: leftHandleView.centerYAnchor).isActive = true
|
||||
leftHandleKnob.centerXAnchor.constraint(equalTo: leftHandleView.centerXAnchor).isActive = true
|
||||
|
||||
rightHandleView.isUserInteractionEnabled = true
|
||||
rightHandleView.layer.cornerRadius = 2.0
|
||||
rightHandleView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(rightHandleView)
|
||||
|
||||
rightHandleView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
|
||||
rightHandleView.widthAnchor.constraint(equalToConstant: handleWidth).isActive = true
|
||||
rightHandleView.rightAnchor.constraint(equalTo: trimView.rightAnchor).isActive = true
|
||||
rightHandleView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
|
||||
|
||||
rightHandleKnob.translatesAutoresizingMaskIntoConstraints = false
|
||||
rightHandleView.addSubview(rightHandleKnob)
|
||||
|
||||
rightHandleKnob.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5).isActive = true
|
||||
rightHandleKnob.widthAnchor.constraint(equalToConstant: 2).isActive = true
|
||||
rightHandleKnob.centerYAnchor.constraint(equalTo: rightHandleView.centerYAnchor).isActive = true
|
||||
rightHandleKnob.centerXAnchor.constraint(equalTo: rightHandleView.centerXAnchor).isActive = true
|
||||
}
|
||||
|
||||
private func setupMaskView() {
|
||||
|
||||
leftMaskView.isUserInteractionEnabled = false
|
||||
leftMaskView.backgroundColor = .white
|
||||
leftMaskView.alpha = 0.7
|
||||
leftMaskView.translatesAutoresizingMaskIntoConstraints = false
|
||||
insertSubview(leftMaskView, belowSubview: leftHandleView)
|
||||
|
||||
leftMaskView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
|
||||
leftMaskView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
leftMaskView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
leftMaskView.rightAnchor.constraint(equalTo: leftHandleView.centerXAnchor).isActive = true
|
||||
|
||||
rightMaskView.isUserInteractionEnabled = false
|
||||
rightMaskView.backgroundColor = .white
|
||||
rightMaskView.alpha = 0.7
|
||||
rightMaskView.translatesAutoresizingMaskIntoConstraints = false
|
||||
insertSubview(rightMaskView, belowSubview: rightHandleView)
|
||||
|
||||
rightMaskView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
|
||||
rightMaskView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
rightMaskView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
rightMaskView.leftAnchor.constraint(equalTo: rightHandleView.centerXAnchor).isActive = true
|
||||
}
|
||||
|
||||
private func setupPositionBar() {
|
||||
|
||||
positionBar.frame = CGRect(x: 0, y: 0, width: 3, height: frame.height)
|
||||
positionBar.backgroundColor = positionBarColor
|
||||
positionBar.center = CGPoint(x: leftHandleView.frame.maxX, y: center.y)
|
||||
positionBar.layer.cornerRadius = 1
|
||||
positionBar.translatesAutoresizingMaskIntoConstraints = false
|
||||
positionBar.isUserInteractionEnabled = false
|
||||
addSubview(positionBar)
|
||||
|
||||
positionBar.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
|
||||
positionBar.widthAnchor.constraint(equalToConstant: 3).isActive = true
|
||||
positionBar.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
|
||||
positionConstraint = positionBar.leftAnchor.constraint(equalTo: leftHandleView.rightAnchor, constant: 0)
|
||||
positionConstraint?.isActive = true
|
||||
}
|
||||
|
||||
private func setupGestures() {
|
||||
|
||||
let leftPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(TrimmerView.handlePanGesture))
|
||||
leftHandleView.addGestureRecognizer(leftPanGestureRecognizer)
|
||||
let rightPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(TrimmerView.handlePanGesture))
|
||||
rightHandleView.addGestureRecognizer(rightPanGestureRecognizer)
|
||||
}
|
||||
|
||||
private func updateMainColor() {
|
||||
trimView.layer.borderColor = mainColor.cgColor
|
||||
leftHandleView.backgroundColor = mainColor
|
||||
rightHandleView.backgroundColor = mainColor
|
||||
}
|
||||
|
||||
private func updateHandleColor() {
|
||||
leftHandleKnob.backgroundColor = handleColor
|
||||
rightHandleKnob.backgroundColor = handleColor
|
||||
}
|
||||
|
||||
// MARK: - Trim Gestures
|
||||
|
||||
@objc func handlePanGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
|
||||
guard let view = gestureRecognizer.view, let superView = gestureRecognizer.view?.superview else { return }
|
||||
let isLeftGesture = view == leftHandleView
|
||||
switch gestureRecognizer.state {
|
||||
|
||||
case .began:
|
||||
if isLeftGesture {
|
||||
currentLeftConstraint = leftConstraint!.constant
|
||||
} else {
|
||||
currentRightConstraint = rightConstraint!.constant
|
||||
}
|
||||
updateSelectedTime(stoppedMoving: false)
|
||||
case .changed:
|
||||
let translation = gestureRecognizer.translation(in: superView)
|
||||
if isLeftGesture {
|
||||
updateLeftConstraint(with: translation)
|
||||
} else {
|
||||
updateRightConstraint(with: translation)
|
||||
}
|
||||
layoutIfNeeded()
|
||||
if let startTime = startTime, isLeftGesture {
|
||||
seek(to: startTime)
|
||||
} else if let endTime = endTime {
|
||||
seek(to: endTime)
|
||||
}
|
||||
updateSelectedTime(stoppedMoving: false)
|
||||
|
||||
case .cancelled, .ended, .failed:
|
||||
updateSelectedTime(stoppedMoving: true)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
private func updateLeftConstraint(with translation: CGPoint) {
|
||||
let maxConstraint = max(rightHandleView.frame.origin.x - handleWidth - minimumDistanceBetweenHandle, 0)
|
||||
let newConstraint = min(max(0, currentLeftConstraint + translation.x), maxConstraint)
|
||||
leftConstraint?.constant = newConstraint
|
||||
}
|
||||
|
||||
private func updateRightConstraint(with translation: CGPoint) {
|
||||
let maxConstraint = min(2 * handleWidth - frame.width + leftHandleView.frame.origin.x + minimumDistanceBetweenHandle, 0)
|
||||
let newConstraint = max(min(0, currentRightConstraint + translation.x), maxConstraint)
|
||||
rightConstraint?.constant = newConstraint
|
||||
}
|
||||
|
||||
// MARK: - Asset loading
|
||||
|
||||
override func assetDidChange(newAsset: AVAsset?) {
|
||||
super.assetDidChange(newAsset: newAsset)
|
||||
resetHandleViewPosition()
|
||||
}
|
||||
|
||||
private func resetHandleViewPosition() {
|
||||
leftConstraint?.constant = 0
|
||||
rightConstraint?.constant = 0
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
// MARK: - Time Equivalence
|
||||
|
||||
/// Move the position bar to the given time.
|
||||
public func seek(to time: CMTime) {
|
||||
if let newPosition = getPosition(from: time) {
|
||||
|
||||
let offsetPosition = newPosition - assetPreview.contentOffset.x - leftHandleView.frame.origin.x
|
||||
let maxPosition = rightHandleView.frame.origin.x - (leftHandleView.frame.origin.x + handleWidth)
|
||||
- positionBar.frame.width
|
||||
let normalizedPosition = min(max(0, offsetPosition), maxPosition)
|
||||
positionConstraint?.constant = normalizedPosition
|
||||
layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
/// The selected start time for the current asset.
|
||||
public var startTime: CMTime? {
|
||||
let startPosition = leftHandleView.frame.origin.x + assetPreview.contentOffset.x
|
||||
return getTime(from: startPosition)
|
||||
}
|
||||
|
||||
/// The selected end time for the current asset.
|
||||
public var endTime: CMTime? {
|
||||
let endPosition = rightHandleView.frame.origin.x + assetPreview.contentOffset.x - handleWidth
|
||||
return getTime(from: endPosition)
|
||||
}
|
||||
|
||||
private func updateSelectedTime(stoppedMoving: Bool) {
|
||||
guard let playerTime = positionBarTime else {
|
||||
return
|
||||
}
|
||||
if stoppedMoving {
|
||||
delegate?.positionBarStoppedMoving(playerTime)
|
||||
} else {
|
||||
delegate?.didChangePositionBar(playerTime)
|
||||
}
|
||||
}
|
||||
|
||||
private var positionBarTime: CMTime? {
|
||||
let barPosition = positionBar.frame.origin.x + assetPreview.contentOffset.x - handleWidth
|
||||
return getTime(from: barPosition)
|
||||
}
|
||||
|
||||
private var minimumDistanceBetweenHandle: CGFloat {
|
||||
guard let asset = asset else { return 0 }
|
||||
return CGFloat(minDuration) * assetPreview.contentView.frame.width / CGFloat(asset.duration.seconds)
|
||||
}
|
||||
|
||||
// MARK: - Scroll View Delegate
|
||||
|
||||
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
updateSelectedTime(stoppedMoving: true)
|
||||
}
|
||||
|
||||
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
if !decelerate {
|
||||
updateSelectedTime(stoppedMoving: true)
|
||||
}
|
||||
}
|
||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
updateSelectedTime(stoppedMoving: false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
# PryntTrimmerView
|
||||
|
||||
[](https://codebeat.co/projects/github-com-prynt-prynttrimmerview-master)
|
||||
[](http://cocoapods.org/pods/PryntTrimmerView)
|
||||
[](http://cocoapods.org/pods/PryntTrimmerView)
|
||||
[](http://cocoapods.org/pods/PryntTrimmerView)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
|
||||
A set of tools written in swift to crop and trim videos.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||
|
||||
### Trimming
|
||||
|
||||

|
||||
|
||||
### Cropping
|
||||
|
||||

|
||||
|
||||
## Requirements
|
||||
|
||||
PryntTrimmerView requires iOS9: It uses Layout Anchors to define the constraints.
|
||||
|
||||
## Installation
|
||||
|
||||
#### CocoaPods
|
||||
|
||||
PryntTrimmerView is available through [CocoaPods](http://cocoapods.org). To install
|
||||
it, simply add the following line to your Podfile:
|
||||
|
||||
```ruby
|
||||
pod "PryntTrimmerView"
|
||||
```
|
||||
|
||||
Then, run `pod install` to download the source and add it to your workspace.
|
||||
|
||||
#### Carthage
|
||||
|
||||
PryntTrimmmerView is available through Carthage. To install
|
||||
it, simply add the following line to your Cartfile:
|
||||
|
||||
```
|
||||
github "HHK1/PryntTrimmerView"
|
||||
```
|
||||
|
||||
Run `carthage update` to build the framework and drag the built PryntTrimmerView.framework into your Xcode project.
|
||||
|
||||
#### Swift Version
|
||||
|
||||
- Swift 3 compatibility: use version 1.0.1 or below.
|
||||
- Swift 4 compatibility: use version 2.x.x.
|
||||
- Swift 4.2 compatibility: use version 3.x.x
|
||||
|
||||
## Usage
|
||||
|
||||
:warning: _This library does not contain an API to crop or trim your video asset. You can find a possible implementation for this in the example pod, but the library only provides the UI._
|
||||
|
||||
### Trimming
|
||||
|
||||
Create a `TrimmerView` instance (in interface builder or through code), and add it to your view hierarchy.
|
||||
|
||||
```
|
||||
trimmerView.asset = asset
|
||||
trimmerView.delegate = self
|
||||
```
|
||||
|
||||
Access the `startTime` and `endTime` property to know where to trim your asset. You can use the `TrimmerViewDelegate` to link the trimmer with an `AVPlayer` and provide the end user with a preview. See the `VideoTrimmerViewController` inside the project to see an example.
|
||||
|
||||
You can also customize the trimmer view by changing its colors:
|
||||
```
|
||||
trimmerView.handleColor = UIColor.white
|
||||
trimmerView.mainColor = UIColor.orange
|
||||
trimmerView.positionBarColor = UIColor.white
|
||||
```
|
||||
|
||||
### Cropping
|
||||
|
||||
Create an instance of the `VideoCropView` and add it to your view hierarchy, then load your video into the crop view: `videoCropView.asset = asset`.
|
||||
|
||||
You can set the aspect ratio you want using the `setAspectRatio` method. Once you are satisfied with the portion of the asset you want to crop, call `getImageCropFrame` to retrieve the select frame. See the `VideoCropperViewController` in the example app for an actual example of how to crop the video for export.
|
||||
|
||||
## License
|
||||
|
||||
PryntTrimmerView is available under the MIT license. See the LICENSE file for more info.
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Acknowledgements
|
||||
This application makes use of the following third party libraries:
|
||||
|
||||
## PryntTrimmerView
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Prynt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
Generated by CocoaPods - https://cocoapods.org
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreferenceSpecifiers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>This application makes use of the following third party libraries:</string>
|
||||
<key>Title</key>
|
||||
<string>Acknowledgements</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>MIT License
|
||||
|
||||
Copyright (c) 2017 Prynt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
</string>
|
||||
<key>License</key>
|
||||
<string>MIT</string>
|
||||
<key>Title</key>
|
||||
<string>PryntTrimmerView</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Generated by CocoaPods - https://cocoapods.org</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>StringsTable</key>
|
||||
<string>Acknowledgements</string>
|
||||
<key>Title</key>
|
||||
<string>Acknowledgements</string>
|
||||
</dict>
|
||||
</plist>
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_Pods_SimpleVideoAnimation : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_Pods_SimpleVideoAnimation
|
||||
@end
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
${PODS_ROOT}/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks.sh
|
||||
${BUILT_PRODUCTS_DIR}/PryntTrimmerView/PryntTrimmerView.framework
|
||||
+1
@@ -0,0 +1 @@
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PryntTrimmerView.framework
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
${PODS_ROOT}/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks.sh
|
||||
${BUILT_PRODUCTS_DIR}/PryntTrimmerView/PryntTrimmerView.framework
|
||||
+1
@@ -0,0 +1 @@
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PryntTrimmerView.framework
|
||||
Generated
Executable
+171
@@ -0,0 +1,171 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
function on_error {
|
||||
echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
|
||||
}
|
||||
trap 'on_error $LINENO' ERR
|
||||
|
||||
if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
|
||||
# If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
|
||||
# frameworks to, so exit 0 (signalling the script phase was successful).
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
|
||||
COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}"
|
||||
SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
|
||||
|
||||
# Used as a return value for each invocation of `strip_invalid_archs` function.
|
||||
STRIP_BINARY_RETVAL=0
|
||||
|
||||
# This protects against multiple targets copying the same framework dependency at the same time. The solution
|
||||
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
|
||||
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
|
||||
|
||||
# Copies and strips a vendored framework
|
||||
install_framework()
|
||||
{
|
||||
if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
|
||||
local source="${BUILT_PRODUCTS_DIR}/$1"
|
||||
elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
|
||||
local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
|
||||
elif [ -r "$1" ]; then
|
||||
local source="$1"
|
||||
fi
|
||||
|
||||
local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
|
||||
if [ -L "${source}" ]; then
|
||||
echo "Symlinked..."
|
||||
source="$(readlink "${source}")"
|
||||
fi
|
||||
|
||||
# Use filter instead of exclude so missing patterns don't throw errors.
|
||||
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
|
||||
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
|
||||
|
||||
local basename
|
||||
basename="$(basename -s .framework "$1")"
|
||||
binary="${destination}/${basename}.framework/${basename}"
|
||||
|
||||
if ! [ -r "$binary" ]; then
|
||||
binary="${destination}/${basename}"
|
||||
elif [ -L "${binary}" ]; then
|
||||
echo "Destination binary is symlinked..."
|
||||
dirname="$(dirname "${binary}")"
|
||||
binary="${dirname}/$(readlink "${binary}")"
|
||||
fi
|
||||
|
||||
# Strip invalid architectures so "fat" simulator / device frameworks work on device
|
||||
if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
|
||||
strip_invalid_archs "$binary"
|
||||
fi
|
||||
|
||||
# Resign the code if required by the build settings to avoid unstable apps
|
||||
code_sign_if_enabled "${destination}/$(basename "$1")"
|
||||
|
||||
# Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
|
||||
if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
|
||||
local swift_runtime_libs
|
||||
swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u)
|
||||
for lib in $swift_runtime_libs; do
|
||||
echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
|
||||
rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
|
||||
code_sign_if_enabled "${destination}/${lib}"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Copies and strips a vendored dSYM
|
||||
install_dsym() {
|
||||
local source="$1"
|
||||
if [ -r "$source" ]; then
|
||||
# Copy the dSYM into a the targets temp dir.
|
||||
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\""
|
||||
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}"
|
||||
|
||||
local basename
|
||||
basename="$(basename -s .framework.dSYM "$source")"
|
||||
binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
|
||||
|
||||
# Strip invalid architectures so "fat" simulator / device frameworks work on device
|
||||
if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then
|
||||
strip_invalid_archs "$binary"
|
||||
fi
|
||||
|
||||
if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
|
||||
# Move the stripped file into its final destination.
|
||||
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
|
||||
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
|
||||
else
|
||||
# The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
|
||||
touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Copies the bcsymbolmap files of a vendored framework
|
||||
install_bcsymbolmap() {
|
||||
local bcsymbolmap_path="$1"
|
||||
local destination="${BUILT_PRODUCTS_DIR}"
|
||||
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}""
|
||||
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"
|
||||
}
|
||||
|
||||
# Signs a framework with the provided identity
|
||||
code_sign_if_enabled() {
|
||||
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
|
||||
# Use the current code_sign_identity
|
||||
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
|
||||
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"
|
||||
|
||||
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
|
||||
code_sign_cmd="$code_sign_cmd &"
|
||||
fi
|
||||
echo "$code_sign_cmd"
|
||||
eval "$code_sign_cmd"
|
||||
fi
|
||||
}
|
||||
|
||||
# Strip invalid architectures
|
||||
strip_invalid_archs() {
|
||||
binary="$1"
|
||||
# Get architectures for current target binary
|
||||
binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
|
||||
# Intersect them with the architectures we are building for
|
||||
intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
|
||||
# If there are no archs supported by this binary then warn the user
|
||||
if [[ -z "$intersected_archs" ]]; then
|
||||
echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
|
||||
STRIP_BINARY_RETVAL=0
|
||||
return
|
||||
fi
|
||||
stripped=""
|
||||
for arch in $binary_archs; do
|
||||
if ! [[ "${ARCHS}" == *"$arch"* ]]; then
|
||||
# Strip non-valid architectures in-place
|
||||
lipo -remove "$arch" -output "$binary" "$binary"
|
||||
stripped="$stripped $arch"
|
||||
fi
|
||||
done
|
||||
if [[ "$stripped" ]]; then
|
||||
echo "Stripped $binary of architectures:$stripped"
|
||||
fi
|
||||
STRIP_BINARY_RETVAL=1
|
||||
}
|
||||
|
||||
|
||||
if [[ "$CONFIGURATION" == "Debug" ]]; then
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/PryntTrimmerView/PryntTrimmerView.framework"
|
||||
fi
|
||||
if [[ "$CONFIGURATION" == "Release" ]]; then
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/PryntTrimmerView/PryntTrimmerView.framework"
|
||||
fi
|
||||
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
|
||||
wait
|
||||
fi
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
FOUNDATION_EXPORT double Pods_SimpleVideoAnimationVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char Pods_SimpleVideoAnimationVersionString[];
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PryntTrimmerView"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PryntTrimmerView/PryntTrimmerView.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_LDFLAGS = $(inherited) -framework "PryntTrimmerView"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
PODS_ROOT = ${SRCROOT}/Pods
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
framework module Pods_SimpleVideoAnimation {
|
||||
umbrella header "Pods-SimpleVideoAnimation-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PryntTrimmerView"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/PryntTrimmerView/PryntTrimmerView.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_LDFLAGS = $(inherited) -framework "PryntTrimmerView"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
PODS_ROOT = ${SRCROOT}/Pods
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-Info.plist
Generated
+26
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_PryntTrimmerView : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_PryntTrimmerView
|
||||
@end
|
||||
Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-prefix.pch
Generated
+12
@@ -0,0 +1,12 @@
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Examples/SimpleVideoAnimation/Pods/Target Support Files/PryntTrimmerView/PryntTrimmerView-umbrella.h
Generated
+16
@@ -0,0 +1,16 @@
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
FOUNDATION_EXPORT double PryntTrimmerViewVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char PryntTrimmerViewVersionString[];
|
||||
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
framework module PryntTrimmerView {
|
||||
umbrella header "PryntTrimmerView-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/PryntTrimmerView
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/PryntTrimmerView
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
||||
@@ -0,0 +1,481 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 51;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0194809395268398D3F15306 /* Pods_SimpleVideoAnimation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16B763834C8C3CCC5972A2A2 /* Pods_SimpleVideoAnimation.framework */; };
|
||||
6A54C20024C7DE9200EF216E /* cute.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 6A54C1FE24C7DE9200EF216E /* cute.mp4 */; };
|
||||
6A874BA024C53EF700ADFD8A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A874B9F24C53EF700ADFD8A /* AppDelegate.swift */; };
|
||||
6A874BA224C53EF700ADFD8A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A874BA124C53EF700ADFD8A /* SceneDelegate.swift */; };
|
||||
6A874BA424C53EF700ADFD8A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A874BA324C53EF700ADFD8A /* ViewController.swift */; };
|
||||
6A874BA724C53EF700ADFD8A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6A874BA524C53EF700ADFD8A /* Main.storyboard */; };
|
||||
6A874BA924C53EF800ADFD8A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A874BA824C53EF800ADFD8A /* Assets.xcassets */; };
|
||||
6A874BAC24C53EF800ADFD8A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6A874BAA24C53EF800ADFD8A /* LaunchScreen.storyboard */; };
|
||||
6A874BB924C5462C00ADFD8A /* MetalVideoProcess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A874BB824C545CC00ADFD8A /* MetalVideoProcess.framework */; };
|
||||
6A874BBA24C5462C00ADFD8A /* MetalVideoProcess.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6A874BB824C545CC00ADFD8A /* MetalVideoProcess.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
6A874BDA24C6FDE200ADFD8A /* 853.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 6A874BD824C6FDE200ADFD8A /* 853.mp4 */; };
|
||||
AB59B95124C9717600943BE2 /* NavigateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB59B94F24C9717600943BE2 /* NavigateViewController.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
6A874BB724C545CC00ADFD8A /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6A874BB324C545CC00ADFD8A /* MetalVideoProcess.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 6ADB062424AD885F0010A817;
|
||||
remoteInfo = MetalVideoProcess;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
6A874BBB24C5462C00ADFD8A /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
6A874BBA24C5462C00ADFD8A /* MetalVideoProcess.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
16B763834C8C3CCC5972A2A2 /* Pods_SimpleVideoAnimation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SimpleVideoAnimation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6A54C1FE24C7DE9200EF216E /* cute.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = cute.mp4; sourceTree = "<group>"; };
|
||||
6A874B9C24C53EF700ADFD8A /* SimpleVideoAnimation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimpleVideoAnimation.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6A874B9F24C53EF700ADFD8A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
6A874BA124C53EF700ADFD8A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
6A874BA324C53EF700ADFD8A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
6A874BA624C53EF700ADFD8A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
6A874BA824C53EF800ADFD8A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
6A874BAB24C53EF800ADFD8A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
6A874BAD24C53EF800ADFD8A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
6A874BB324C545CC00ADFD8A /* MetalVideoProcess.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MetalVideoProcess.xcodeproj; path = ../../MetalVideoProcess.xcodeproj; sourceTree = "<group>"; };
|
||||
6A874BD824C6FDE200ADFD8A /* 853.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; name = 853.mp4; path = SimpleVIdeoAnimation/853.mp4; sourceTree = SOURCE_ROOT; };
|
||||
A512CD4DD2F5AB414CF01FE6 /* Pods-SimpleVideoAnimation.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SimpleVideoAnimation.release.xcconfig"; path = "Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.release.xcconfig"; sourceTree = "<group>"; };
|
||||
AB59B94F24C9717600943BE2 /* NavigateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NavigateViewController.swift; path = SimpleVIdeoAnimation/NavigateViewController.swift; sourceTree = SOURCE_ROOT; };
|
||||
D107CF0887B69DDEA376CBEA /* Pods-SimpleVideoAnimation.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SimpleVideoAnimation.debug.xcconfig"; path = "Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
6A874B9924C53EF700ADFD8A /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6A874BB924C5462C00ADFD8A /* MetalVideoProcess.framework in Frameworks */,
|
||||
0194809395268398D3F15306 /* Pods_SimpleVideoAnimation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
09662178662A79DB0CFDDB51 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
16B763834C8C3CCC5972A2A2 /* Pods_SimpleVideoAnimation.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6A874B9324C53EF700ADFD8A = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6A874BB324C545CC00ADFD8A /* MetalVideoProcess.xcodeproj */,
|
||||
6A874B9E24C53EF700ADFD8A /* SimpleVideoAnimation */,
|
||||
6A874B9D24C53EF700ADFD8A /* Products */,
|
||||
99120D7228A6AD7ADD24AEFC /* Pods */,
|
||||
09662178662A79DB0CFDDB51 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6A874B9D24C53EF700ADFD8A /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6A874B9C24C53EF700ADFD8A /* SimpleVideoAnimation.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6A874B9E24C53EF700ADFD8A /* SimpleVideoAnimation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6A874BD824C6FDE200ADFD8A /* 853.mp4 */,
|
||||
6A54C1FE24C7DE9200EF216E /* cute.mp4 */,
|
||||
6A874B9F24C53EF700ADFD8A /* AppDelegate.swift */,
|
||||
6A874BA124C53EF700ADFD8A /* SceneDelegate.swift */,
|
||||
6A874BA324C53EF700ADFD8A /* ViewController.swift */,
|
||||
AB59B94F24C9717600943BE2 /* NavigateViewController.swift */,
|
||||
6A874BA524C53EF700ADFD8A /* Main.storyboard */,
|
||||
6A874BA824C53EF800ADFD8A /* Assets.xcassets */,
|
||||
6A874BAA24C53EF800ADFD8A /* LaunchScreen.storyboard */,
|
||||
6A874BAD24C53EF800ADFD8A /* Info.plist */,
|
||||
);
|
||||
path = SimpleVideoAnimation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6A874BB424C545CC00ADFD8A /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6A874BB824C545CC00ADFD8A /* MetalVideoProcess.framework */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
99120D7228A6AD7ADD24AEFC /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D107CF0887B69DDEA376CBEA /* Pods-SimpleVideoAnimation.debug.xcconfig */,
|
||||
A512CD4DD2F5AB414CF01FE6 /* Pods-SimpleVideoAnimation.release.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
6A874B9B24C53EF700ADFD8A /* SimpleVideoAnimation */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 6A874BB024C53EF800ADFD8A /* Build configuration list for PBXNativeTarget "SimpleVideoAnimation" */;
|
||||
buildPhases = (
|
||||
F6586DA6B62A1C6F6FBAC023 /* [CP] Check Pods Manifest.lock */,
|
||||
6A874B9824C53EF700ADFD8A /* Sources */,
|
||||
6A874B9924C53EF700ADFD8A /* Frameworks */,
|
||||
6A874B9A24C53EF700ADFD8A /* Resources */,
|
||||
D3C3047B9CCE5998654663D7 /* [CP] Embed Pods Frameworks */,
|
||||
6A874BBB24C5462C00ADFD8A /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = SimpleVideoAnimation;
|
||||
productName = SimpleVideoAnimation;
|
||||
productReference = 6A874B9C24C53EF700ADFD8A /* SimpleVideoAnimation.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
6A874B9424C53EF700ADFD8A /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1160;
|
||||
LastUpgradeCheck = 1160;
|
||||
ORGANIZATIONNAME = "RenZhu Macro";
|
||||
TargetAttributes = {
|
||||
6A874B9B24C53EF700ADFD8A = {
|
||||
CreatedOnToolsVersion = 11.6;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 6A874B9724C53EF700ADFD8A /* Build configuration list for PBXProject "SimpleVideoAnimation" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 6A874B9324C53EF700ADFD8A;
|
||||
productRefGroup = 6A874B9D24C53EF700ADFD8A /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = 6A874BB424C545CC00ADFD8A /* Products */;
|
||||
ProjectRef = 6A874BB324C545CC00ADFD8A /* MetalVideoProcess.xcodeproj */;
|
||||
},
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
6A874B9B24C53EF700ADFD8A /* SimpleVideoAnimation */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXReferenceProxy section */
|
||||
6A874BB824C545CC00ADFD8A /* MetalVideoProcess.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = MetalVideoProcess.framework;
|
||||
remoteRef = 6A874BB724C545CC00ADFD8A /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
6A874B9A24C53EF700ADFD8A /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6A874BAC24C53EF800ADFD8A /* LaunchScreen.storyboard in Resources */,
|
||||
6A874BA924C53EF800ADFD8A /* Assets.xcassets in Resources */,
|
||||
6A874BDA24C6FDE200ADFD8A /* 853.mp4 in Resources */,
|
||||
6A54C20024C7DE9200EF216E /* cute.mp4 in Resources */,
|
||||
6A874BA724C53EF700ADFD8A /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
D3C3047B9CCE5998654663D7 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
F6586DA6B62A1C6F6FBAC023 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-SimpleVideoAnimation-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
6A874B9824C53EF700ADFD8A /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6A874BA424C53EF700ADFD8A /* ViewController.swift in Sources */,
|
||||
AB59B95124C9717600943BE2 /* NavigateViewController.swift in Sources */,
|
||||
6A874BA024C53EF700ADFD8A /* AppDelegate.swift in Sources */,
|
||||
6A874BA224C53EF700ADFD8A /* SceneDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
6A874BA524C53EF700ADFD8A /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
6A874BA624C53EF700ADFD8A /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6A874BAA24C53EF800ADFD8A /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
6A874BAB24C53EF800ADFD8A /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
6A874BAE24C53EF800ADFD8A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.6;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
6A874BAF24C53EF800ADFD8A /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.6;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
6A874BB124C53EF800ADFD8A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D107CF0887B69DDEA376CBEA /* Pods-SimpleVideoAnimation.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = RYRPKMVKDL;
|
||||
INFOPLIST_FILE = SimpleVideoAnimation/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.wangrenzhu.SimpleVideoAnimation;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
6A874BB224C53EF800ADFD8A /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = A512CD4DD2F5AB414CF01FE6 /* Pods-SimpleVideoAnimation.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = RYRPKMVKDL;
|
||||
INFOPLIST_FILE = SimpleVideoAnimation/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.wangrenzhu.SimpleVideoAnimation;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
6A874B9724C53EF700ADFD8A /* Build configuration list for PBXProject "SimpleVideoAnimation" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
6A874BAE24C53EF800ADFD8A /* Debug */,
|
||||
6A874BAF24C53EF800ADFD8A /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
6A874BB024C53EF800ADFD8A /* Build configuration list for PBXNativeTarget "SimpleVideoAnimation" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
6A874BB124C53EF800ADFD8A /* Debug */,
|
||||
6A874BB224C53EF800ADFD8A /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 6A874B9424C53EF700ADFD8A /* Project object */;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:SimpleVideoAnimation.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1160"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6A874B9B24C53EF700ADFD8A"
|
||||
BuildableName = "SimpleVideoAnimation.app"
|
||||
BlueprintName = "SimpleVideoAnimation"
|
||||
ReferencedContainer = "container:SimpleVideoAnimation.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUFrameCaptureMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6A874B9B24C53EF700ADFD8A"
|
||||
BuildableName = "SimpleVideoAnimation.app"
|
||||
BlueprintName = "SimpleVideoAnimation"
|
||||
ReferencedContainer = "container:SimpleVideoAnimation.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6A874B9B24C53EF700ADFD8A"
|
||||
BuildableName = "SimpleVideoAnimation.app"
|
||||
BlueprintName = "SimpleVideoAnimation"
|
||||
ReferencedContainer = "container:SimpleVideoAnimation.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
Binary file not shown.
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// SimpleVideoAnimation
|
||||
//
|
||||
// Created by RenZhu Macro on 2020/7/20.
|
||||
// Copyright © 2020 RenZhu Macro. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: UISceneSession Lifecycle
|
||||
|
||||
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||
// Called when a new scene session is being created.
|
||||
// Use this method to select a configuration to create the new scene with.
|
||||
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
|
||||
// Called when the user discards a scene session.
|
||||
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
|
||||
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17132" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Sjl-30-bHq">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17105.1"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ViewController" id="BYZ-38-t0r" customClass="ViewController" customModule="SimpleVideoAnimation" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FXj-LY-59h" customClass="MetalVideoProcessRenderView" customModule="MetalVideoProcess">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="438"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="FXj-LY-59h" secondAttribute="height" id="iBP-Gu-Wj9"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gLe-Zn-niL">
|
||||
<rect key="frame" x="231" y="562" width="42" height="30"/>
|
||||
<state key="normal" title="Pause"/>
|
||||
<connections>
|
||||
<action selector="pause:" destination="BYZ-38-t0r" eventType="touchUpInside" id="jXA-Ko-2E5"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9lP-KN-D4J">
|
||||
<rect key="frame" x="136" y="562" width="30" height="30"/>
|
||||
<state key="normal" title="Play"/>
|
||||
<connections>
|
||||
<action selector="play:" destination="BYZ-38-t0r" eventType="touchUpInside" id="BxN-gS-f9o"/>
|
||||
</connections>
|
||||
</button>
|
||||
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JkB-hg-7A6" customClass="TrimmerView" customModule="PryntTrimmerView">
|
||||
<rect key="frame" x="0.0" y="470" width="375" height="80"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="80" id="9zr-ar-gfx"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="9lP-KN-D4J" firstAttribute="top" secondItem="JkB-hg-7A6" secondAttribute="bottom" constant="10" id="EVm-H0-xCG"/>
|
||||
<constraint firstItem="FXj-LY-59h" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" id="Hbb-9e-X3i"/>
|
||||
<constraint firstItem="gLe-Zn-niL" firstAttribute="top" secondItem="9lP-KN-D4J" secondAttribute="bottom" constant="10" id="RPS-Ea-blL"/>
|
||||
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="JkB-hg-7A6" secondAttribute="trailing" id="SQ5-rc-Ie3"/>
|
||||
<constraint firstItem="9lP-KN-D4J" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="T7k-7C-oA5"/>
|
||||
<constraint firstItem="gLe-Zn-niL" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="Z0h-3k-NiZ"/>
|
||||
<constraint firstItem="JkB-hg-7A6" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="bea-xO-Fs7"/>
|
||||
<constraint firstItem="FXj-LY-59h" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="cew-hA-jt7"/>
|
||||
<constraint firstItem="JkB-hg-7A6" firstAttribute="top" secondItem="FXj-LY-59h" secondAttribute="bottom" constant="10" id="rCV-zQ-Eyi"/>
|
||||
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="FXj-LY-59h" secondAttribute="trailing" id="rUK-5t-TAP"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="progressView" destination="JkB-hg-7A6" id="eky-zy-L0Z"/>
|
||||
<outlet property="renderView" destination="FXj-LY-59h" id="8pT-q1-cV2"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="132" y="137.18140929535232"/>
|
||||
</scene>
|
||||
<!--NavigateView-->
|
||||
<scene sceneID="XBJ-QK-b6X">
|
||||
<objects>
|
||||
<viewController id="Sjl-30-bHq" userLabel="NavigateView" customClass="NavigateViewController" customModule="SimpleVideoAnimation" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="aeg-zc-cBS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<viewLayoutGuide key="safeArea" id="qlq-zn-i27"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Aj9-BM-fNL" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-906" y="137"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// NavigateViewController.swift
|
||||
// SimpleVideoTransition
|
||||
//
|
||||
// Created by Ruanshengqiang Macro on 2020/7/2.
|
||||
// Copyright © 2020 Ruanshengqiang Macro. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MetalVideoProcess
|
||||
import AVFoundation
|
||||
import MetalVideoProcess
|
||||
|
||||
class NavigateViewController: UIViewController {
|
||||
@IBOutlet weak var naviStackView: UIStackView!
|
||||
|
||||
var transitionTimeRange = CMTimeRange.init()
|
||||
|
||||
let N = 19
|
||||
private func getTransition(index: Int) -> (titleName: String, transObject: MetalVideoProcessMotion) {
|
||||
switch index {
|
||||
case 0:
|
||||
return (titleName: "渐显", transObject: MetalVideoProcessFadeInMotion())
|
||||
case 1:
|
||||
return (titleName: "轻微放大", transObject: MetalVideoProcessSlimZoomInMotion())
|
||||
case 2:
|
||||
return (titleName: "放大", transObject: MetalVideoProcessZoomInMotion())
|
||||
case 3:
|
||||
return (titleName: "缩小", transObject: MetalVideoProcessZoomOutMotion())
|
||||
case 4:
|
||||
return (titleName: "向左滑入", transObject: MetalVideoProcessMoveLeftMotion())
|
||||
case 5:
|
||||
return (titleName: "向右滑入", transObject: MetalVideoProcessMoveRightMotion())
|
||||
case 6:
|
||||
return (titleName: "向下滑动", transObject: MetalVideoProcessMoveDownMotion())
|
||||
case 7:
|
||||
return (titleName: "向上滑动", transObject: MetalVideoProcessMoveUpMotion())
|
||||
case 8:
|
||||
return (titleName: "旋转", transObject: MetalVideoProcessRotateMotion())
|
||||
case 9:
|
||||
return (titleName: "涡旋旋转", transObject: MetalVideoProcessSwirlMotion())
|
||||
case 10:
|
||||
return (titleName: "镜像旋转", transObject: MetalVideoProcessMirrorRotateMotion())
|
||||
case 11:
|
||||
return (titleName: "向上旋入", transObject: MetalVideoProcessUpMoveInBlurMotion())
|
||||
case 12:
|
||||
return (titleName: "向上旋入II", transObject: MetalVideoProcessUpMoveInBlurIIMotion())
|
||||
case 13:
|
||||
return (titleName: "雨刷", transObject: MetalVideoProcessWiperMotion())
|
||||
case 14:
|
||||
return (titleName: "摆钟", transObject: MetalVideoProcessPendulumMotion())
|
||||
case 15:
|
||||
return (titleName: "向右甩入", transObject: MetalVideoProcessRightDropMotion())
|
||||
case 16:
|
||||
return (titleName: "向下甩入", transObject: MetalVideoProcessUpDropMotion())
|
||||
case 17:
|
||||
return (titleName: "动感放大", transObject: MetalVideoProcessZoomInBlurMotion())
|
||||
case 18:
|
||||
return (titleName: "动感缩小", transObject: MetalVideoProcessZoomOutBlurMotion())
|
||||
/*
|
||||
case 15:
|
||||
return (titleName: "向右转入", transObject: MetalVideoProcessRotateInRightMotion())
|
||||
|
||||
*/
|
||||
default:
|
||||
return (titleName: "放大", transObject: MetalVideoProcessMoveUpMotion())
|
||||
|
||||
}
|
||||
}
|
||||
private func getButton(index: Int) -> UIButton {
|
||||
let splitNum = 5.0
|
||||
let pieceWidth = Double(self.view.frame.width) / splitNum
|
||||
let buttonWidth = pieceWidth * 0.8
|
||||
let dIndex = Double(index)
|
||||
let indexX = fmod(dIndex, splitNum) * pieceWidth
|
||||
let indexY = (floor(dIndex / splitNum)) * pieceWidth + 100.0
|
||||
|
||||
let buttonFrame = CGRect(x: indexX, y: indexY, width: buttonWidth, height: buttonWidth)
|
||||
|
||||
let but = UIButton(frame: buttonFrame)
|
||||
but.backgroundColor = UIColor(red: 0.3, green: 0.4, blue: 0.3, alpha: 0.7)
|
||||
but.setTitle(getTransition(index: index).titleName, for: .normal)
|
||||
|
||||
but.titleLabel?.adjustsFontSizeToFitWidth = true
|
||||
but.addTarget(self, action: #selector(self.swithController(_:)), for: .touchUpInside)
|
||||
|
||||
but.tag = index
|
||||
return but
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
for index in 0...N - 1 {
|
||||
self.view.addSubview(getButton(index: index))
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func swithController(_ sender: UIButton) {
|
||||
|
||||
let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController") as! ViewController
|
||||
|
||||
let trans = getTransition(index: sender.tag)
|
||||
|
||||
viewController.motionIn = trans.transObject
|
||||
|
||||
self.present(viewController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// SceneDelegate.swift
|
||||
// SimpleVideoAnimation
|
||||
//
|
||||
// Created by RenZhu Macro on 2020/7/20.
|
||||
// Copyright © 2020 RenZhu Macro. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
|
||||
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
||||
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
|
||||
guard let _ = (scene as? UIWindowScene) else { return }
|
||||
}
|
||||
|
||||
func sceneDidDisconnect(_ scene: UIScene) {
|
||||
// Called as the scene is being released by the system.
|
||||
// This occurs shortly after the scene enters the background, or when its session is discarded.
|
||||
// Release any resources associated with this scene that can be re-created the next time the scene connects.
|
||||
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
|
||||
}
|
||||
|
||||
func sceneDidBecomeActive(_ scene: UIScene) {
|
||||
// Called when the scene has moved from an inactive state to an active state.
|
||||
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
|
||||
}
|
||||
|
||||
func sceneWillResignActive(_ scene: UIScene) {
|
||||
// Called when the scene will move from an active state to an inactive state.
|
||||
// This may occur due to temporary interruptions (ex. an incoming phone call).
|
||||
}
|
||||
|
||||
func sceneWillEnterForeground(_ scene: UIScene) {
|
||||
// Called as the scene transitions from the background to the foreground.
|
||||
// Use this method to undo the changes made on entering the background.
|
||||
}
|
||||
|
||||
func sceneDidEnterBackground(_ scene: UIScene) {
|
||||
// Called as the scene transitions from the foreground to the background.
|
||||
// Use this method to save data, release shared resources, and store enough scene-specific state information
|
||||
// to restore the scene back to its current state.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
//
|
||||
// ViewController.swift
|
||||
// SimpleVideoAnimation
|
||||
//
|
||||
// Created by RenZhu Macro on 2020/7/20.
|
||||
// Copyright © 2020 RenZhu Macro. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
import MetalVideoProcess
|
||||
import PryntTrimmerView
|
||||
|
||||
|
||||
class ViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var renderView: MetalVideoProcessRenderView!
|
||||
|
||||
@IBOutlet weak var progressView: TrimmerView!
|
||||
|
||||
var player: MetalVideoProcessPlayer?
|
||||
var editor: MetalVideoEditor?
|
||||
public var motionIn: MetalVideoProcessMotion = MetalVideoProcessMoveUpMotion()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
let asset1 = AVAsset(url: Bundle.main.url(forResource: "853", withExtension: "mp4")!)
|
||||
|
||||
let item1 = MetalVideoEditorItem(asset: asset1)
|
||||
|
||||
do {
|
||||
let editor = try MetalVideoEditor(videoItems: [item1])
|
||||
self.editor = editor
|
||||
let playerItem = editor.buildPlayerItem()
|
||||
|
||||
let fadeIn = motionIn
|
||||
fadeIn.timingType = .quadraticEaseOut
|
||||
|
||||
let fadeOut = MetalVideoProcessFadeOutMotion()
|
||||
fadeOut.timingType = .quarticEaseOut
|
||||
|
||||
let fadeInTimeRange = CMTimeRangeMake(start: CMTime(seconds: 0.0), duration: CMTime(seconds: 2.0))
|
||||
fadeIn.saveUniformSettings(forTimelineRange: fadeInTimeRange, trackID: item1.trackID)
|
||||
|
||||
let fadeOutTimeRange = CMTimeRange(start: item1.timeRange.end - CMTime(seconds: 4.0), duration: CMTime(seconds: 1.5))
|
||||
fadeOut.saveUniformSettings(forTimelineRange: fadeOutTimeRange, trackID: item1.trackID)
|
||||
|
||||
let player = try MetalVideoProcessPlayer(playerItem: playerItem)
|
||||
player.playerDelegate = self
|
||||
|
||||
let background = MetalVideoProcessBackground(trackID: 0)
|
||||
background.aspectRatioType = .Ratio9_16
|
||||
background.canvasSizeType = .Type720p
|
||||
background.setBackgroundType(type: .Blur)
|
||||
|
||||
let transform = MetalVideoProcessTransformFilter()
|
||||
transform.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID)
|
||||
transform.roi = CGRect(x: 0.5, y: 0.5, width: 0.5, height: 0.5)
|
||||
fadeIn.roi = transform.roi
|
||||
player.addTarget(background, atTargetIndex: 0, trackID: item1.trackID, targetTrackId: 0)
|
||||
|
||||
background --> fadeIn //source 0
|
||||
player.addTarget(transform, atTargetIndex: nil, trackID: item1.trackID, targetTrackId: 0) //source 1
|
||||
|
||||
transform --> fadeIn --> renderView //source 1
|
||||
|
||||
self.player = player
|
||||
} catch {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
guard let asset = self.editor?.playerItem?.asset else {
|
||||
return
|
||||
}
|
||||
self.progressView.asset = asset
|
||||
self.progressView.delegate = self
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
self.player?.suspend()
|
||||
self.player?.dispose()
|
||||
}
|
||||
|
||||
@IBAction func play(_ sender: Any) {
|
||||
self.player?.play()
|
||||
}
|
||||
|
||||
@IBAction func pause(_ sender: Any) {
|
||||
self.player?.pause()
|
||||
}
|
||||
|
||||
@IBAction func progressChanged(_ sender: Any) {
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: TrimmerViewDelegate {
|
||||
func didChangePositionBar(_ playerTime: CMTime) {
|
||||
self.player?.seekTo(time: playerTime.seconds)
|
||||
}
|
||||
|
||||
func positionBarStoppedMoving(_ playerTime: CMTime) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: MetalVideoProcessPlayerDelegate {
|
||||
func playbackFrameTimeChanged(frameTime time: CMTime, player: AVPlayer) {
|
||||
if player.rate == 0.0 {
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.progressView.seek(to: time)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func playEnded(currentPlayer player: AVPlayer) {
|
||||
|
||||
}
|
||||
|
||||
func finishExport(error: NSError?) {
|
||||
|
||||
}
|
||||
|
||||
func exportProgressChanged(_ progress: Float) {
|
||||
|
||||
}
|
||||
}
|
||||
Binary file not shown.
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:SimpleVideoAnimation.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -572,7 +572,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = RYRPKMVKDL;
|
||||
DEVELOPMENT_TEAM = 3BJYKWHMM4;
|
||||
INFOPLIST_FILE = SimpleVideoEditor/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -591,7 +591,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = RYRPKMVKDL;
|
||||
DEVELOPMENT_TEAM = 3BJYKWHMM4;
|
||||
INFOPLIST_FILE = SimpleVideoEditor/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
||||
+2
-1
@@ -136,8 +136,9 @@ extension ViewController: MetalVideoProcessPlayerDelegate {
|
||||
source --> self.movieWriter!
|
||||
self.view.isUserInteractionEnabled = false
|
||||
|
||||
try? self.player?.startExport()
|
||||
|
||||
self.movieWriter?.activateAudioTrack()
|
||||
self.movieWriter?.startRecording()
|
||||
try? self.player?.startExport()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ extension ViewController: UIGestureRecognizerDelegate {
|
||||
let tanl = ges.translation(in: ges.view)
|
||||
|
||||
// have to content the view scaleFactor and the vertex to texture scale.
|
||||
transformFilter.translation = Position(currentPostion.x + Float(tanl.x * self.renderView.contentScaleFactor * 2.0) / Float(self.renderView.drawableSize.width), currentPostion.y - Float(tanl.y * self.renderView.contentScaleFactor * 2.0) / Float(self.renderView.drawableSize.height))
|
||||
transformFilter.translation = Position(currentPostion.x + Float(tanl.x * self.renderView.contentScaleFactor * 2.0) / Float(self.renderView.drawableSize.width), 1.0 - currentPostion.y - Float(tanl.y * self.renderView.contentScaleFactor * 2.0) / Float(self.renderView.drawableSize.height))
|
||||
|
||||
} else if ges.state == .ended {
|
||||
currentPostion = (transformFilter.translation)
|
||||
|
||||
@@ -385,7 +385,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = RYRPKMVKDL;
|
||||
DEVELOPMENT_TEAM = 3BJYKWHMM4;
|
||||
INFOPLIST_FILE = SimpleVideoExport/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -405,7 +405,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = RYRPKMVKDL;
|
||||
DEVELOPMENT_TEAM = 3BJYKWHMM4;
|
||||
INFOPLIST_FILE = SimpleVideoExport/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
||||
@@ -68,7 +68,10 @@ class ViewController: UIViewController {
|
||||
transform1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID)
|
||||
|
||||
transform2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
let layer1 = MetalVideoProcessBlendFilter()
|
||||
|
||||
videoBackground --> layer1
|
||||
@@ -115,9 +118,10 @@ class ViewController: UIViewController {
|
||||
source --> self.movieWriter!
|
||||
self.view.isUserInteractionEnabled = false
|
||||
|
||||
try? self.player?.startExport()
|
||||
|
||||
self.movieWriter?.activateAudioTrack()
|
||||
self.movieWriter?.startRecording()
|
||||
try? self.player?.startExport()
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
6ADB07A924ADDE450010A817 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6ADB07A724ADDE450010A817 /* Main.storyboard */; };
|
||||
6ADB07AB24ADDE460010A817 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6ADB07AA24ADDE460010A817 /* Assets.xcassets */; };
|
||||
6ADB07AE24ADDE460010A817 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6ADB07AC24ADDE460010A817 /* LaunchScreen.storyboard */; };
|
||||
AB59B8CC24C5AC8000943BE2 /* NaviViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB59B8CA24C5AC8000943BE2 /* NaviViewController.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -55,6 +56,7 @@
|
||||
6ADB07AA24ADDE460010A817 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
6ADB07AD24ADDE460010A817 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
6ADB07AF24ADDE460010A817 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
AB59B8CA24C5AC8000943BE2 /* NaviViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NaviViewController.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -107,6 +109,7 @@
|
||||
children = (
|
||||
6A3E708C24C067C3009A0AC9 /* 853.mp4 */,
|
||||
6A3E708E24C067C3009A0AC9 /* cute.mp4 */,
|
||||
AB59B8CA24C5AC8000943BE2 /* NaviViewController.swift */,
|
||||
6ADB07A124ADDE450010A817 /* AppDelegate.swift */,
|
||||
6ADB07A324ADDE450010A817 /* SceneDelegate.swift */,
|
||||
6ADB07A524ADDE450010A817 /* ViewController.swift */,
|
||||
@@ -146,7 +149,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1150;
|
||||
LastUpgradeCheck = 1150;
|
||||
LastUpgradeCheck = 1200;
|
||||
ORGANIZATIONNAME = "RenZhu Macro";
|
||||
TargetAttributes = {
|
||||
6ADB079D24ADDE450010A817 = {
|
||||
@@ -208,6 +211,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
AB59B8CC24C5AC8000943BE2 /* NaviViewController.swift in Sources */,
|
||||
6ADB07A624ADDE450010A817 /* ViewController.swift in Sources */,
|
||||
6ADB07A224ADDE450010A817 /* AppDelegate.swift in Sources */,
|
||||
6ADB07A424ADDE450010A817 /* SceneDelegate.swift in Sources */,
|
||||
@@ -262,6 +266,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -322,6 +327,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -355,7 +361,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = RYRPKMVKDL;
|
||||
DEVELOPMENT_TEAM = 3BJYKWHMM4;
|
||||
INFOPLIST_FILE = SimpleVideoTransition/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -374,7 +380,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = RYRPKMVKDL;
|
||||
DEVELOPMENT_TEAM = 3BJYKWHMM4;
|
||||
INFOPLIST_FILE = SimpleVideoTransition/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6ADB079D24ADDE450010A817"
|
||||
BuildableName = "SimpleVideoTransition.app"
|
||||
BlueprintName = "SimpleVideoTransition"
|
||||
ReferencedContainer = "container:SimpleVideoTransition.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<AdditionalOptions>
|
||||
<AdditionalOption
|
||||
key = "NSZombieEnabled"
|
||||
value = "YES"
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
</AdditionalOptions>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6ADB079D24ADDE450010A817"
|
||||
BuildableName = "SimpleVideoTransition.app"
|
||||
BlueprintName = "SimpleVideoTransition"
|
||||
ReferencedContainer = "container:SimpleVideoTransition.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6ADB079D24ADDE450010A817"
|
||||
BuildableName = "SimpleVideoTransition.app"
|
||||
BlueprintName = "SimpleVideoTransition"
|
||||
ReferencedContainer = "container:SimpleVideoTransition.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
Binary file not shown.
@@ -1,142 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17132" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="AUR-96-71u">
|
||||
<device id="retina5_9" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17105.1"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="SimpleRealtimeFilterPlayback" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController storyboardIdentifier="ViewController" id="BYZ-38-t0r" customClass="ViewController" customModule="SimpleVideoTransition" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Dc5-Dz-opT" customClass="MetalVideoProcessRenderView" customModule="MetalVideoProcess">
|
||||
<rect key="frame" x="0.0" y="44.000000000000028" width="375" height="468.66666666666674"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="Dc5-Dz-opT" secondAttribute="height" multiplier="0.8" id="86r-Og-HwX"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="vYk-6H-dKP">
|
||||
<rect key="frame" x="8" y="517.66666666666663" width="359" height="31"/>
|
||||
<slider opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="vYk-6H-dKP">
|
||||
<rect key="frame" x="2" y="576" width="359" height="31"/>
|
||||
<connections>
|
||||
<action selector="progressChanged:" destination="BYZ-38-t0r" eventType="valueChanged" id="fwh-uk-uYl"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="K6w-Ud-xUK">
|
||||
<rect key="frame" x="172.66666666666666" y="552.66666666666663" width="30" height="30"/>
|
||||
<state key="normal" title="Play"/>
|
||||
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Dc5-Dz-opT" customClass="MetalVideoProcessRenderView" customModule="MetalVideoProcess">
|
||||
<rect key="frame" x="0.0" y="100" width="375" height="468.66666666666674"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="Dc5-Dz-opT" secondAttribute="height" multiplier="0.8" id="86r-Og-HwX"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8dD-NO-sG4">
|
||||
<rect key="frame" x="192" y="612" width="69" height="42"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="42" id="M3N-FO-erk"/>
|
||||
</constraints>
|
||||
<state key="normal" title="pause"/>
|
||||
<connections>
|
||||
<action selector="play:" destination="BYZ-38-t0r" eventType="touchUpInside" id="cZ4-2E-l3J"/>
|
||||
<action selector="pause:" destination="BYZ-38-t0r" eventType="touchUpInside" id="koL-AD-Vdy"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Blur track1:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ieB-aM-Q6C">
|
||||
<rect key="frame" x="20" y="663.66666666666663" width="108" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Gray video:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8Il-25-Tij">
|
||||
<rect key="frame" x="104" y="709.66666666666663" width="87" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Beauty track1:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9fu-53-ZHp">
|
||||
<rect key="frame" x="20" y="622.66666666666663" width="109" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="track2:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Y6k-OT-zIp">
|
||||
<rect key="frame" x="208" y="622.66666666666663" width="54" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="track2:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wCL-xj-Wni">
|
||||
<rect key="frame" x="207" y="663.66666666666663" width="54" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="Lbm-mS-3au">
|
||||
<rect key="frame" x="139" y="617.66666666666663" width="51" height="31"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="L7o-SP-KhR">
|
||||
<rect key="frame" x="104" y="612" width="69" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="69" id="5g0-CY-YQ2"/>
|
||||
</constraints>
|
||||
<state key="normal" title="play"/>
|
||||
<connections>
|
||||
<action selector="filterOn:" destination="BYZ-38-t0r" eventType="valueChanged" id="f2R-IT-cw2"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<switch opaque="NO" tag="1" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="rJO-mU-xYY">
|
||||
<rect key="frame" x="138" y="658.66666666666663" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="filterOn:" destination="BYZ-38-t0r" eventType="valueChanged" id="yx1-uC-eEF"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<switch opaque="NO" tag="2" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="eJu-z5-owd">
|
||||
<rect key="frame" x="272" y="617.66666666666663" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="filterOn:" destination="BYZ-38-t0r" eventType="valueChanged" id="9Y9-ZR-JFD"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<switch opaque="NO" tag="3" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="Ufa-eY-5ZC">
|
||||
<rect key="frame" x="271" y="658.66666666666663" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="filterOn:" destination="BYZ-38-t0r" eventType="valueChanged" id="Bsk-J9-CjP"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<switch opaque="NO" tag="4" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="tbD-8b-LUS">
|
||||
<rect key="frame" x="201" y="704.66666666666663" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="filterOn:" destination="BYZ-38-t0r" eventType="valueChanged" id="J3N-K6-5di"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gMy-fV-lJB">
|
||||
<rect key="frame" x="166.66666666666666" y="587.66666666666663" width="42" height="30"/>
|
||||
<state key="normal" title="Pause"/>
|
||||
<connections>
|
||||
<action selector="pause:" destination="BYZ-38-t0r" eventType="touchUpInside" id="WbY-hn-U3f"/>
|
||||
<action selector="play:" destination="BYZ-38-t0r" eventType="touchUpInside" id="ec4-6v-Fe0"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="eJu-z5-owd" firstAttribute="leading" secondItem="Y6k-OT-zIp" secondAttribute="trailing" constant="10" id="0Ih-iu-pAo"/>
|
||||
<constraint firstItem="Y6k-OT-zIp" firstAttribute="centerY" secondItem="Lbm-mS-3au" secondAttribute="centerY" id="5T7-S9-kXH"/>
|
||||
<constraint firstItem="wCL-xj-Wni" firstAttribute="leading" secondItem="rJO-mU-xYY" secondAttribute="trailing" constant="20" id="76t-qu-CCa"/>
|
||||
<constraint firstItem="Dc5-Dz-opT" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" id="7MR-S1-7Xq"/>
|
||||
<constraint firstItem="Lbm-mS-3au" firstAttribute="leading" secondItem="9fu-53-ZHp" secondAttribute="trailing" constant="10" id="8Uu-ol-v3J"/>
|
||||
<constraint firstItem="vYk-6H-dKP" firstAttribute="top" secondItem="Dc5-Dz-opT" secondAttribute="bottom" constant="5" id="9FO-ag-qBs"/>
|
||||
<constraint firstItem="Dc5-Dz-opT" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="BlF-9Y-vIn"/>
|
||||
<constraint firstItem="gMy-fV-lJB" firstAttribute="top" secondItem="K6w-Ud-xUK" secondAttribute="bottom" constant="5" id="Gi5-pV-rO9"/>
|
||||
<constraint firstItem="gMy-fV-lJB" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="Gqx-bc-4zc"/>
|
||||
<constraint firstItem="Ufa-eY-5ZC" firstAttribute="centerY" secondItem="wCL-xj-Wni" secondAttribute="centerY" id="Hgc-Ac-M81"/>
|
||||
<constraint firstItem="rJO-mU-xYY" firstAttribute="leading" secondItem="ieB-aM-Q6C" secondAttribute="trailing" constant="10" id="Iwt-Iv-Se2"/>
|
||||
<constraint firstItem="K6w-Ud-xUK" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="LaJ-so-qEc"/>
|
||||
<constraint firstItem="8Il-25-Tij" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" constant="-40" id="OpR-uB-WtM"/>
|
||||
<constraint firstItem="L7o-SP-KhR" firstAttribute="top" secondItem="vYk-6H-dKP" secondAttribute="bottom" constant="6" id="FQa-7W-Nj8"/>
|
||||
<constraint firstItem="8dD-NO-sG4" firstAttribute="leading" secondItem="L7o-SP-KhR" secondAttribute="trailing" constant="19" id="Fjg-ZY-9uz"/>
|
||||
<constraint firstItem="L7o-SP-KhR" firstAttribute="top" secondItem="8dD-NO-sG4" secondAttribute="top" id="INb-AW-t9N"/>
|
||||
<constraint firstItem="6Tk-OE-BBY" firstAttribute="bottom" secondItem="L7o-SP-KhR" secondAttribute="bottom" constant="113" id="N7W-JJ-rFn"/>
|
||||
<constraint firstItem="L7o-SP-KhR" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="104" id="P4W-wv-UMV"/>
|
||||
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="Dc5-Dz-opT" secondAttribute="trailing" id="Rt7-yT-k9J"/>
|
||||
<constraint firstItem="vYk-6H-dKP" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="10" id="UKf-H0-pvm"/>
|
||||
<constraint firstItem="tbD-8b-LUS" firstAttribute="centerY" secondItem="8Il-25-Tij" secondAttribute="centerY" id="Vqa-LB-raz"/>
|
||||
<constraint firstItem="8Il-25-Tij" firstAttribute="top" secondItem="rJO-mU-xYY" secondAttribute="bottom" constant="20" id="WcG-FP-5ve"/>
|
||||
<constraint firstItem="wCL-xj-Wni" firstAttribute="centerY" secondItem="rJO-mU-xYY" secondAttribute="centerY" id="ZZc-6F-iqE"/>
|
||||
<constraint firstItem="9fu-53-ZHp" firstAttribute="top" secondItem="gMy-fV-lJB" secondAttribute="bottom" constant="5" id="a0O-Xf-ftz"/>
|
||||
<constraint firstItem="ieB-aM-Q6C" firstAttribute="top" secondItem="9fu-53-ZHp" secondAttribute="bottom" constant="20" id="aEl-4X-oGJ"/>
|
||||
<constraint firstItem="K6w-Ud-xUK" firstAttribute="top" secondItem="vYk-6H-dKP" secondAttribute="bottom" constant="5" id="aYl-Sa-qn2"/>
|
||||
<constraint firstItem="tbD-8b-LUS" firstAttribute="leading" secondItem="8Il-25-Tij" secondAttribute="trailing" constant="10" id="eiC-rb-qZN"/>
|
||||
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="8dD-NO-sG4" secondAttribute="trailing" constant="114" id="ajW-8P-48Y"/>
|
||||
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="vYk-6H-dKP" secondAttribute="trailing" constant="10" id="itv-bP-sNm"/>
|
||||
<constraint firstItem="Y6k-OT-zIp" firstAttribute="leading" secondItem="Lbm-mS-3au" secondAttribute="trailing" constant="20" id="jyW-v3-sUE"/>
|
||||
<constraint firstItem="ieB-aM-Q6C" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="20" id="lU2-BQ-chN"/>
|
||||
<constraint firstItem="Ufa-eY-5ZC" firstAttribute="leading" secondItem="wCL-xj-Wni" secondAttribute="trailing" constant="10" id="sM2-LG-67H"/>
|
||||
<constraint firstItem="Lbm-mS-3au" firstAttribute="centerY" secondItem="9fu-53-ZHp" secondAttribute="centerY" id="sef-Y8-TX1"/>
|
||||
<constraint firstItem="rJO-mU-xYY" firstAttribute="centerY" secondItem="ieB-aM-Q6C" secondAttribute="centerY" id="uRA-u6-Tw6"/>
|
||||
<constraint firstItem="9fu-53-ZHp" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="20" id="ynQ-x2-1Fe"/>
|
||||
<constraint firstItem="eJu-z5-owd" firstAttribute="centerY" secondItem="Y6k-OT-zIp" secondAttribute="centerY" id="z7O-GK-or8"/>
|
||||
<constraint firstItem="6Tk-OE-BBY" firstAttribute="bottom" secondItem="8dD-NO-sG4" secondAttribute="bottom" constant="124" id="mjl-wp-mzn"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="progress" destination="vYk-6H-dKP" id="qNJ-VV-aFL"/>
|
||||
@@ -145,7 +76,27 @@
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="131.8840579710145" y="137.94642857142856"/>
|
||||
<point key="canvasLocation" x="130.40000000000001" y="137.4384236453202"/>
|
||||
</scene>
|
||||
<!--NaviViewController-->
|
||||
<scene sceneID="kl0-RY-FE2">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="NaviViewController" id="AUR-96-71u" userLabel="NaviViewController" customClass="NaviViewController" customModule="SimpleVideoTransition" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="uf9-x0-sSc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<viewLayoutGuide key="safeArea" id="jjf-eS-Sa2"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="6D3-vd-i7x" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-959" y="137"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// ResourceItem.swift
|
||||
// SimpleVideoEditor
|
||||
//
|
||||
// Created by RenZhu Macro on 2020/7/7.
|
||||
// Copyright © 2020 RenZhu Macro. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MetalVideoProcess
|
||||
|
||||
class ResourceItem: MetalVideoEditorItem {
|
||||
|
||||
public var rotate = 0.0
|
||||
public var scale = Position(1.0, 1.0)
|
||||
//translation 为归一化
|
||||
public var translation = Position(0.0, 0.0)
|
||||
|
||||
var orientation : UIInterfaceOrientation = .portrait
|
||||
weak var transformFilter: MetalVideoProcessTransformFilter?
|
||||
weak var currentLayer: MetalVideoProcessBlendFilter?
|
||||
var roi: CGRect = CGRect.zero
|
||||
var fillType: MetalVideoProcessTransformFilter.StretchType = .aspectToFill
|
||||
|
||||
var isSelected: Bool = false
|
||||
|
||||
var startTimeText: NSString {
|
||||
get {
|
||||
return NSString(format: "%.2f", self.startTime.seconds)
|
||||
}
|
||||
}
|
||||
|
||||
var durationText: NSString {
|
||||
get {
|
||||
return NSString(format: "%.2f", self.duration.seconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// NaviViewController.swift
|
||||
// SimpleVideoTransition
|
||||
//
|
||||
// Created by RenZhu Macro on 2020/7/2.
|
||||
// Copyright © 2020 RenZhu Macro. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MetalVideoProcess
|
||||
import AVFoundation
|
||||
import MetalVideoProcess
|
||||
|
||||
class NaviViewController: UIViewController {
|
||||
@IBOutlet weak var naviStackView: UIStackView!
|
||||
|
||||
var transitionTimeRange = CMTimeRange.init()
|
||||
|
||||
let N = 14
|
||||
private func getTransition(index: Int) -> (titleName: String, transObject: MetalVideoProcessTransition) {
|
||||
switch index {
|
||||
case 0:
|
||||
return (titleName: "倒影", transObject: MetalVideoProcessReflectTransition())
|
||||
case 1:
|
||||
return (titleName: "闪白", transObject: MetalVideoProcessShanBaiTransition())
|
||||
case 2:
|
||||
return (titleName: "向下擦除", transObject: MetalVideoProcessEraseDownTransition())
|
||||
case 3:
|
||||
return (titleName: "叠化", transObject: MetalVideoProcessFadeTransition())
|
||||
case 4:
|
||||
return (titleName: "镜像翻转", transObject: MetalVideoProcessMirrorRotateTransition())
|
||||
case 5:
|
||||
return (titleName: "燃烧", transObject: MetalVideoProcessBurnTransition())
|
||||
case 6:
|
||||
return (titleName: "立方体", transObject: MetalVideoProcessCubeTransition())
|
||||
case 7:
|
||||
return (titleName: "向上擦除", transObject: MetalVideoProcessEraseUpTransition())
|
||||
case 8:
|
||||
return (titleName: "向左擦除", transObject: MetalVideoProcessEraseLeftTransition())
|
||||
case 9:
|
||||
return (titleName: "向右擦除", transObject: MetalVideoProcessEraseRightTransition())
|
||||
case 10:
|
||||
return (titleName: "变形", transObject: MetalVideoProcessMorphTransition())
|
||||
case 11:
|
||||
return (titleName: "圆形蒙版", transObject: MetalVideoProcessCircleEraseTransition())
|
||||
case 12:
|
||||
return (titleName: "上移", transObject: MetalVideoProcessVerticalUpGlitchTransition())
|
||||
default:
|
||||
return (titleName: "倒影", transObject: MetalVideoProcessReflectTransition())
|
||||
}
|
||||
}
|
||||
private func getButton(index: Int) -> UIButton {
|
||||
let splitNum = 5.0
|
||||
let pieceWidth = Double(self.view.frame.width) / splitNum
|
||||
let buttonWidth = pieceWidth * 0.8
|
||||
let dIndex = Double(index)
|
||||
let indexX = fmod(dIndex, splitNum) * pieceWidth
|
||||
let indexY = (floor(dIndex / splitNum)) * pieceWidth + 100.0
|
||||
|
||||
let buttonFrame = CGRect(x: indexX, y: indexY, width: buttonWidth, height: buttonWidth)
|
||||
|
||||
let but = UIButton(frame: buttonFrame)
|
||||
but.backgroundColor = UIColor(red: 0.3, green: 0.4, blue: 0.3, alpha: 0.7)
|
||||
but.setTitle(getTransition(index: index).titleName, for: .normal)
|
||||
|
||||
but.titleLabel?.adjustsFontSizeToFitWidth = true
|
||||
but.addTarget(self, action: #selector(self.swithController(_:)), for: .touchUpInside)
|
||||
|
||||
but.tag = index
|
||||
return but
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
for index in 0...N - 1 {
|
||||
self.view.addSubview(getButton(index: index))
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func swithController(_ sender: UIButton) {
|
||||
|
||||
let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController") as! ViewController
|
||||
|
||||
let trans = getTransition(index: sender.tag)
|
||||
|
||||
viewController.transition = trans.transObject
|
||||
|
||||
self.present(viewController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
//
|
||||
// ResourceEditorViewController.swift
|
||||
// SimpleVideoEditor
|
||||
//
|
||||
// Created by RenZhu Macro on 2020/7/8.
|
||||
// Copyright © 2020 RenZhu Macro. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MetalVideoProcess
|
||||
import AVFoundation
|
||||
|
||||
class ResourceItemEditView: UIViewController {
|
||||
|
||||
deinit {
|
||||
|
||||
}
|
||||
|
||||
public weak var resourceItem: ResourceItem?
|
||||
|
||||
@IBOutlet weak var timeRangeText: UILabel!
|
||||
@IBOutlet weak var renderView: MetalVideoProcessRenderView!
|
||||
|
||||
@IBOutlet weak var startTimeSlider: UISlider!
|
||||
|
||||
@IBOutlet weak var durationSlider: UISlider!
|
||||
|
||||
@IBOutlet weak var positionSlider: UISlider!
|
||||
|
||||
var player: MetalVideoProcessPlayer?
|
||||
var editor: MetalVideoEditor?
|
||||
var assetDuration: CMTime = .zero
|
||||
|
||||
@IBOutlet weak var isMuteSwitch: UISwitch!
|
||||
var isPipItem: Bool = false
|
||||
var isImageResource: Bool = false
|
||||
|
||||
public func loadResourceItem(_ item: ResourceItem, isPipItem: Bool = false) {
|
||||
self.isPipItem = isPipItem
|
||||
self.resourceItem = item
|
||||
self.isMuteSwitch.isOn = item.isMute
|
||||
|
||||
if let imageResource = item.resource as? ImageResource {
|
||||
self.isImageResource = true
|
||||
//picture
|
||||
let mtlTexture = imageResource.sourceTexture(at: .zero)!
|
||||
let texture = Texture(orientation: .landscapeLeft, texture: mtlTexture, timingStyle: .stillImage)
|
||||
self.renderView.newTextureAvailable(texture, fromSourceIndex: 0, trackID: 0)
|
||||
|
||||
|
||||
} else if let asset = (item.resource as? AVAssetTrackResource)?.asset {
|
||||
assetDuration = asset.duration
|
||||
self.editor = try? MetalVideoEditor(videoItems: [item])
|
||||
|
||||
|
||||
guard let playerItem = self.editor?.buildPlayerItem() else {
|
||||
return
|
||||
}
|
||||
|
||||
self.player = try? MetalVideoProcessPlayer(playerItem: playerItem)
|
||||
|
||||
let orientation = orientationForTrack(asset: asset)
|
||||
|
||||
let transform = MetalVideoProcessTransformFilter()
|
||||
|
||||
switch orientation {
|
||||
case .portrait:
|
||||
break
|
||||
case .landscapeLeft:
|
||||
transform.rotate = 90.0
|
||||
break
|
||||
case .landscapeRight:
|
||||
transform.rotate = -90.0
|
||||
break
|
||||
case .portraitUpsideDown:
|
||||
transform.rotate = 180.0
|
||||
break
|
||||
default:
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
self.player?.addTarget(transform, atTargetIndex: nil, trackID: item.trackID, targetTrackId: 0)
|
||||
transform --> self.renderView
|
||||
} else {
|
||||
return //unsupported
|
||||
}
|
||||
|
||||
|
||||
|
||||
self.startTimeSlider.minimumValue = 0.0
|
||||
|
||||
//min value of a item is 0.1 seconds
|
||||
self.durationSlider.minimumValue = 0.1
|
||||
if self.isImageResource {
|
||||
self.durationSlider.maximumValue = 100.0
|
||||
assetDuration = CMTime(seconds: 100.0)
|
||||
} else {
|
||||
self.durationSlider.maximumValue = Float(assetDuration.seconds)
|
||||
}
|
||||
|
||||
self.startTimeSlider.maximumValue = self.durationSlider.maximumValue - 0.1
|
||||
|
||||
self.startTimeSlider.value = Float(item.resource.selectedTimeRange.start.seconds)
|
||||
self.durationSlider.value = Float(item.resource.selectedTimeRange.duration.seconds)
|
||||
self.positionSlider.value = Float(item.startTime.seconds)
|
||||
|
||||
if self.isPipItem {
|
||||
self.positionSlider.isEnabled = true
|
||||
self.positionSlider.maximumValue = 100.0
|
||||
self.positionSlider.value = Float(item.startTime.seconds)
|
||||
}
|
||||
|
||||
timeRangeText.text = NSString(format: "s:%.2f d:%.2f p:%.2f",
|
||||
self.startTimeSlider.value,
|
||||
self.durationSlider.value,
|
||||
self.positionSlider.value) as String
|
||||
|
||||
self.player?.seekTo(time: 0.0)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
self.player?.suspend()
|
||||
self.player?.dispose()
|
||||
self.player = nil
|
||||
self.editor = nil
|
||||
}
|
||||
|
||||
@IBAction func positionValueChanged(_ sender: Any) {
|
||||
self.resourceItem?.startTime = CMTime(seconds: self.positionSlider.value)
|
||||
|
||||
|
||||
timeRangeText.text = NSString(format: "s:%.2f d:%.2f p:%.2f",
|
||||
self.startTimeSlider.value,
|
||||
self.durationSlider.value,
|
||||
self.positionSlider.value) as String
|
||||
}
|
||||
|
||||
@IBAction func startTimeValueChanged(_ sender: Any) {
|
||||
let totalValue = self.startTimeSlider.value + self.durationSlider.value
|
||||
if totalValue > Float(self.assetDuration.seconds) {
|
||||
self.durationSlider.value = Float(self.assetDuration.seconds) - self.startTimeSlider.value
|
||||
}
|
||||
|
||||
resourceItem?.resource.selectedTimeRange = CMTimeRange(start: CMTime(seconds: self.startTimeSlider.value),
|
||||
duration: CMTime(seconds: self.durationSlider.value))
|
||||
if self.isPipItem {
|
||||
self.player?.seekTo(time: Float64(resourceItem?.resource.selectedTimeRange.start.seconds ?? 0.0))
|
||||
} else {
|
||||
self.player?.seekTo(time: Float64(self.startTimeSlider.value))
|
||||
}
|
||||
timeRangeText.text = NSString(format: "s:%.2f d:%.2f p:%.2f",
|
||||
self.startTimeSlider.value,
|
||||
self.durationSlider.value,
|
||||
self.positionSlider.value) as String
|
||||
|
||||
}
|
||||
|
||||
@IBAction func DurationValueChanged(_ sender: Any) {
|
||||
let totalValue = self.startTimeSlider.value + self.durationSlider.value
|
||||
if totalValue > Float(self.assetDuration.seconds) {
|
||||
self.startTimeSlider.value = Float(self.assetDuration.seconds) - self.durationSlider.value
|
||||
}
|
||||
resourceItem?.resource.selectedTimeRange = CMTimeRange(start: CMTime(seconds: self.startTimeSlider.value),
|
||||
duration: CMTime(seconds: self.durationSlider.value))
|
||||
timeRangeText.text = NSString(format: "s:%.2f d:%.2f p:%.2f",
|
||||
self.startTimeSlider.value,
|
||||
self.durationSlider.value,
|
||||
self.positionSlider.value) as String
|
||||
if self.isImageResource {
|
||||
//No need to render the image with frameTime
|
||||
return
|
||||
}
|
||||
self.player?.seekTo(time: Float64(self.startTimeSlider.value + self.durationSlider.value))
|
||||
}
|
||||
|
||||
|
||||
@IBAction func isMuteOn(_ sender: UISwitch) {
|
||||
self.resourceItem?.isMute = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func play(_ sender: Any) {
|
||||
guard let playerItem = self.editor?.buildPlayerItem() else {
|
||||
return
|
||||
}
|
||||
self.player?.updatePlayerItem(playerItem: playerItem)
|
||||
|
||||
self.player?.play()
|
||||
}
|
||||
|
||||
@IBAction func pause(_ sender: Any) {
|
||||
self.player?.pause()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ResourceItemEditView" customModule="SimpleVideoEditor" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="durationSlider" destination="20Y-8z-Bjc" id="TLj-DB-WrL"/>
|
||||
<outlet property="isMuteSwitch" destination="Tbd-ZT-wkM" id="QeO-8E-9m5"/>
|
||||
<outlet property="positionSlider" destination="d5k-3R-pnJ" id="DvC-D3-lX9"/>
|
||||
<outlet property="renderView" destination="33l-v0-rcB" id="qUN-dQ-weP"/>
|
||||
<outlet property="startTimeSlider" destination="BaE-Cm-F7T" id="oAw-GL-BIQ"/>
|
||||
<outlet property="timeRangeText" destination="bli-Ir-iaf" id="OhB-Zg-Ggc"/>
|
||||
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="455" height="540"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IS1-mO-qkH">
|
||||
<rect key="frame" x="0.0" y="0.0" width="455" height="540"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="s:0.0 d:0.0 p:0.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bli-Ir-iaf">
|
||||
<rect key="frame" x="178.5" y="10" width="98" height="16"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="33l-v0-rcB" customClass="MetalVideoProcessRenderView" customModule="MetalVideoProcess">
|
||||
<rect key="frame" x="20" y="54.5" width="415" height="207.5"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="33l-v0-rcB" secondAttribute="height" multiplier="2" id="OOq-Fz-7JE"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Edit TimeRange" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XLW-Lu-VBj">
|
||||
<rect key="frame" x="152.5" y="25" width="138" height="19.5"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="138" id="AeL-Uh-bdN"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
|
||||
<color key="textColor" red="0.33333333333333331" green="0.33333333333333331" blue="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="BaE-Cm-F7T">
|
||||
<rect key="frame" x="103" y="277.5" width="344" height="31"/>
|
||||
<connections>
|
||||
<action selector="startTimeValueChanged:" destination="-1" eventType="valueChanged" id="0Iy-N5-Ge1"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="StartTime" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nTw-8t-hm2">
|
||||
<rect key="frame" x="20" y="282" width="75" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Duration" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sor-n1-AOA">
|
||||
<rect key="frame" x="20" y="322.5" width="65.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Is Mute" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MW9-K9-XrY">
|
||||
<rect key="frame" x="20" y="363" width="56.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="20Y-8z-Bjc">
|
||||
<rect key="frame" x="103" y="318" width="344" height="31"/>
|
||||
<connections>
|
||||
<action selector="DurationValueChanged:" destination="-1" eventType="valueChanged" id="6Au-ng-P7h"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="PipPos" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gt2-Up-oP5">
|
||||
<rect key="frame" x="20" y="393.5" width="52.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="d5k-3R-pnJ">
|
||||
<rect key="frame" x="103" y="337.5" width="344" height="133.5"/>
|
||||
<connections>
|
||||
<action selector="positionValueChanged:" destination="-1" eventType="valueChanged" id="7yz-Bl-8rV"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="Tbd-ZT-wkM">
|
||||
<rect key="frame" x="86.5" y="358" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="isMuteOn:" destination="-1" eventType="valueChanged" id="4qd-1l-V2g"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="9Lx-c3-ISl">
|
||||
<rect key="frame" x="127.5" y="480" width="200" height="50"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="oaz-x9-lZJ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="100" height="50"/>
|
||||
<state key="normal" title="Play"/>
|
||||
<connections>
|
||||
<action selector="play:" destination="-1" eventType="touchUpInside" id="ZH2-JZ-Oe0"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bcQ-85-RzQ">
|
||||
<rect key="frame" x="100" y="0.0" width="100" height="50"/>
|
||||
<state key="normal" title="Pause"/>
|
||||
<connections>
|
||||
<action selector="pause:" destination="-1" eventType="touchUpInside" id="LaU-La-JcS"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="200" id="8AT-OT-jXM"/>
|
||||
<constraint firstAttribute="height" constant="50" id="9ct-2A-OAg"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="20Y-8z-Bjc" secondAttribute="trailing" constant="10" id="1tK-Ho-DAZ"/>
|
||||
<constraint firstItem="MW9-K9-XrY" firstAttribute="leading" secondItem="IS1-mO-qkH" secondAttribute="leading" constant="20" id="8Bl-kg-UIr"/>
|
||||
<constraint firstItem="sor-n1-AOA" firstAttribute="top" secondItem="nTw-8t-hm2" secondAttribute="bottom" constant="20" id="8G0-jo-tS1"/>
|
||||
<constraint firstItem="bli-Ir-iaf" firstAttribute="top" secondItem="IS1-mO-qkH" secondAttribute="top" constant="10" id="95P-fp-0Gf"/>
|
||||
<constraint firstItem="MW9-K9-XrY" firstAttribute="top" secondItem="sor-n1-AOA" secondAttribute="bottom" constant="20" id="CcT-dv-0zy"/>
|
||||
<constraint firstItem="9Lx-c3-ISl" firstAttribute="centerX" secondItem="IS1-mO-qkH" secondAttribute="centerX" id="Dxy-lQ-Lm4"/>
|
||||
<constraint firstItem="33l-v0-rcB" firstAttribute="leading" secondItem="IS1-mO-qkH" secondAttribute="leading" constant="20" id="FCu-9q-iOm"/>
|
||||
<constraint firstItem="sor-n1-AOA" firstAttribute="leading" secondItem="nTw-8t-hm2" secondAttribute="leading" id="Fi7-GU-iU1"/>
|
||||
<constraint firstItem="nTw-8t-hm2" firstAttribute="leading" secondItem="IS1-mO-qkH" secondAttribute="leading" constant="20" id="I16-bg-OVX"/>
|
||||
<constraint firstAttribute="trailing" secondItem="33l-v0-rcB" secondAttribute="trailing" constant="20" id="Ige-mZ-iAm"/>
|
||||
<constraint firstItem="bli-Ir-iaf" firstAttribute="centerX" secondItem="IS1-mO-qkH" secondAttribute="centerX" id="Iv0-mA-qmg"/>
|
||||
<constraint firstItem="d5k-3R-pnJ" firstAttribute="centerY" secondItem="gt2-Up-oP5" secondAttribute="centerY" id="LM2-Im-44w"/>
|
||||
<constraint firstItem="d5k-3R-pnJ" firstAttribute="width" secondItem="20Y-8z-Bjc" secondAttribute="width" id="LVm-d0-8dR"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="gt2-Up-oP5" secondAttribute="bottom" constant="10" id="QUg-zR-ipe"/>
|
||||
<constraint firstAttribute="trailing" secondItem="BaE-Cm-F7T" secondAttribute="trailing" constant="10" id="RBN-jo-twB"/>
|
||||
<constraint firstAttribute="trailing" secondItem="d5k-3R-pnJ" secondAttribute="trailing" constant="10" id="RBN-l1-Kbw"/>
|
||||
<constraint firstItem="BaE-Cm-F7T" firstAttribute="centerY" secondItem="nTw-8t-hm2" secondAttribute="centerY" id="Vty-Xf-bzY"/>
|
||||
<constraint firstItem="BaE-Cm-F7T" firstAttribute="leading" secondItem="nTw-8t-hm2" secondAttribute="trailing" constant="10" id="Vz0-dI-O0O"/>
|
||||
<constraint firstItem="9Lx-c3-ISl" firstAttribute="top" secondItem="d5k-3R-pnJ" secondAttribute="bottom" constant="10" id="WSu-0F-xF0"/>
|
||||
<constraint firstItem="20Y-8z-Bjc" firstAttribute="width" secondItem="BaE-Cm-F7T" secondAttribute="width" id="XH2-i9-bU4"/>
|
||||
<constraint firstItem="Tbd-ZT-wkM" firstAttribute="leading" secondItem="MW9-K9-XrY" secondAttribute="trailing" constant="10" id="azZ-Ho-4Qs"/>
|
||||
<constraint firstItem="Tbd-ZT-wkM" firstAttribute="centerY" secondItem="MW9-K9-XrY" secondAttribute="centerY" id="cAG-JT-Czu"/>
|
||||
<constraint firstItem="gt2-Up-oP5" firstAttribute="leading" secondItem="IS1-mO-qkH" secondAttribute="leading" constant="20" id="cAr-6U-TbS"/>
|
||||
<constraint firstItem="20Y-8z-Bjc" firstAttribute="centerY" secondItem="sor-n1-AOA" secondAttribute="centerY" id="e8d-yu-mVB"/>
|
||||
<constraint firstItem="gt2-Up-oP5" firstAttribute="top" secondItem="MW9-K9-XrY" secondAttribute="bottom" constant="10" id="edR-PI-BxV"/>
|
||||
<constraint firstItem="nTw-8t-hm2" firstAttribute="top" secondItem="33l-v0-rcB" secondAttribute="bottom" constant="20" id="gdq-Qh-R11"/>
|
||||
<constraint firstItem="33l-v0-rcB" firstAttribute="top" secondItem="XLW-Lu-VBj" secondAttribute="bottom" constant="10" id="kzg-iM-HlJ"/>
|
||||
<constraint firstAttribute="bottom" secondItem="9Lx-c3-ISl" secondAttribute="bottom" constant="10" id="lqB-g6-vhJ"/>
|
||||
<constraint firstItem="XLW-Lu-VBj" firstAttribute="top" secondItem="IS1-mO-qkH" secondAttribute="top" constant="25" id="mf9-b7-p4a"/>
|
||||
<constraint firstItem="XLW-Lu-VBj" firstAttribute="centerX" secondItem="IS1-mO-qkH" secondAttribute="centerX" constant="-6" id="pR8-bb-Q05"/>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="450" id="xGd-EA-30x"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="IS1-mO-qkH" secondAttribute="bottom" id="d8w-V8-fpT"/>
|
||||
<constraint firstItem="IS1-mO-qkH" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="nIN-WD-C0j"/>
|
||||
<constraint firstItem="IS1-mO-qkH" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="ncB-9S-Iub"/>
|
||||
<constraint firstAttribute="trailing" secondItem="IS1-mO-qkH" secondAttribute="trailing" id="rlD-9w-zuN"/>
|
||||
</constraints>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="380.43478260869568" y="387.05357142857139"/>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="VAH-Ff-ctu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="42" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<point key="canvasLocation" x="197" y="623"/>
|
||||
</label>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// ResourceItemTableViewCell.swift
|
||||
// SimpleVideoEditor
|
||||
//
|
||||
// Created by RenZhu Macro on 2020/7/8.
|
||||
// Copyright © 2020 RenZhu Macro. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ResourceItemTableViewCell: UITableViewCell {
|
||||
@IBOutlet weak var trackIdLabel: UILabel!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
// Initialization code
|
||||
}
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
super.setSelected(selected, animated: animated)
|
||||
|
||||
// Configure the view for the selected state
|
||||
}
|
||||
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Header.h
|
||||
// SimpleVideoExport
|
||||
//
|
||||
// Created by RenZhu Macro on 2020/7/7.
|
||||
// Copyright © 2020 RenZhu Macro. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ProgressHUD.h"
|
||||
#import "MBProgressHUD.h"
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
//
|
||||
// BlurLayer.swift
|
||||
// DynamicBlurView
|
||||
//
|
||||
// Created by Kyohei Ito on 2017/08/14.
|
||||
// Copyright © 2017年 kyohei_ito. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
private extension CGRect {
|
||||
func rectangle(_ s: CGSize) -> CGRect {
|
||||
let x = origin.x / s.width
|
||||
let y = origin.y / s.height
|
||||
let width = size.width / s.width
|
||||
let height = size.height / s.height
|
||||
return CGRect(x: x, y: y, width: width, height: height)
|
||||
}
|
||||
}
|
||||
|
||||
class BlurLayer: CALayer {
|
||||
private static let blurRadiusKey = "blurRadius"
|
||||
private static let blurLayoutKey = "blurLayout"
|
||||
@NSManaged var blurRadius: CGFloat
|
||||
@NSManaged private var blurLayout: CGFloat
|
||||
|
||||
private var fromBlurRadius: CGFloat?
|
||||
var presentationRadius: CGFloat {
|
||||
if let radius = fromBlurRadius {
|
||||
if let layer = presentation() {
|
||||
return layer.blurRadius
|
||||
} else {
|
||||
return radius
|
||||
}
|
||||
} else {
|
||||
return blurRadius
|
||||
}
|
||||
}
|
||||
|
||||
override class func needsDisplay(forKey key: String) -> Bool {
|
||||
if key == blurRadiusKey || key == blurLayoutKey {
|
||||
return true
|
||||
}
|
||||
return super.needsDisplay(forKey: key)
|
||||
}
|
||||
|
||||
open override func action(forKey event: String) -> CAAction? {
|
||||
if event == BlurLayer.blurRadiusKey {
|
||||
fromBlurRadius = nil
|
||||
|
||||
if let action = super.action(forKey: "opacity") as? CABasicAnimation {
|
||||
fromBlurRadius = (presentation() ?? self).blurRadius
|
||||
|
||||
action.keyPath = event
|
||||
action.fromValue = fromBlurRadius
|
||||
return action
|
||||
}
|
||||
}
|
||||
|
||||
if event == BlurLayer.blurLayoutKey, let action = super.action(forKey: "opacity") as? CABasicAnimation {
|
||||
action.keyPath = event
|
||||
action.fromValue = 0
|
||||
action.toValue = 1
|
||||
return action
|
||||
}
|
||||
|
||||
return super.action(forKey: event)
|
||||
}
|
||||
}
|
||||
|
||||
extension BlurLayer {
|
||||
func draw(_ image: UIImage, fixes isFixes: Bool, baseLayer: CALayer?) {
|
||||
contents = image.cgImage
|
||||
contentsScale = image.scale
|
||||
|
||||
if isFixes, let blurLayer = presentation() {
|
||||
contentsRect = blurLayer.convert(blurLayer.bounds, to: baseLayer).rectangle(image.size)
|
||||
}
|
||||
}
|
||||
|
||||
func refresh() {
|
||||
fromBlurRadius = nil
|
||||
}
|
||||
|
||||
func animate() {
|
||||
UIView.performWithoutAnimation {
|
||||
blurLayout = 0
|
||||
}
|
||||
blurLayout = 1
|
||||
}
|
||||
|
||||
func render(in context: CGContext, for layer: CALayer) {
|
||||
let layers = hideOverlappingLayers(layer.sublayers)
|
||||
layer.render(in: context)
|
||||
layers.forEach {
|
||||
$0.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
private func hideOverlappingLayers(_ layers: [CALayer]?) -> [CALayer] {
|
||||
var hiddenLayers: [CALayer] = []
|
||||
guard let layers = layers else {
|
||||
return hiddenLayers
|
||||
}
|
||||
|
||||
for layer in layers.reversed() {
|
||||
if isHang(to: layer) {
|
||||
return hiddenLayers + hideOverlappingLayers(layer.sublayers)
|
||||
}
|
||||
if layer.isHidden == false {
|
||||
layer.isHidden = true
|
||||
hiddenLayers.append(layer)
|
||||
}
|
||||
if layer == self {
|
||||
return hiddenLayers
|
||||
}
|
||||
}
|
||||
return hiddenLayers
|
||||
}
|
||||
|
||||
private func isHang(to target: CALayer) -> Bool {
|
||||
var layer = superlayer
|
||||
while layer != nil {
|
||||
if layer == target {
|
||||
return true
|
||||
}
|
||||
layer = layer?.superlayer
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// CGContext+CGImage.swift
|
||||
// DynamicBlurView
|
||||
//
|
||||
// Created by Kyohei Ito on 2017/08/17.
|
||||
// Copyright © 2017年 kyohei_ito. All rights reserved.
|
||||
//
|
||||
|
||||
extension CGContext {
|
||||
static func imageContext(with quality: CaptureQuality, rect: CGRect, opaque: Bool) -> CGContext? {
|
||||
UIGraphicsBeginImageContextWithOptions(rect.size, opaque, quality.imageScale)
|
||||
guard let context = UIGraphicsGetCurrentContext() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
context.translateBy(x: -rect.origin.x, y: -rect.origin.y)
|
||||
context.interpolationQuality = quality.interpolationQuality
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
func makeImage(with blendColor: UIColor?, blendMode: CGBlendMode, size: CGSize) -> CGImage? {
|
||||
if let color = blendColor {
|
||||
setFillColor(color.cgColor)
|
||||
setBlendMode(blendMode)
|
||||
fill(CGRect(origin: .zero, size: size))
|
||||
}
|
||||
|
||||
return makeImage()
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// CGImage+Accelerate.swift
|
||||
// DynamicBlurView
|
||||
//
|
||||
// Created by Kyohei Ito on 2017/08/17.
|
||||
// Copyright © 2017年 kyohei_ito. All rights reserved.
|
||||
//
|
||||
|
||||
import Accelerate
|
||||
|
||||
extension CGImage {
|
||||
var area: Int {
|
||||
return width * height
|
||||
}
|
||||
|
||||
private var size: CGSize {
|
||||
return CGSize(width: width, height: height)
|
||||
}
|
||||
|
||||
private var bytes: Int {
|
||||
return bytesPerRow * height
|
||||
}
|
||||
|
||||
private func imageBuffer(from data: UnsafeMutableRawPointer!) -> vImage_Buffer {
|
||||
return vImage_Buffer(data: data, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: bytesPerRow)
|
||||
}
|
||||
|
||||
func blurred(with boxSize: UInt32, iterations: Int, blendColor: UIColor?, blendMode: CGBlendMode) -> CGImage? {
|
||||
guard let providerData = dataProvider?.data else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let inData = malloc(bytes)
|
||||
var inBuffer = imageBuffer(from: inData)
|
||||
|
||||
let outData = malloc(bytes)
|
||||
var outBuffer = imageBuffer(from: outData)
|
||||
|
||||
let tempSize = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, nil, 0, 0, boxSize, boxSize, nil, vImage_Flags(kvImageEdgeExtend + kvImageGetTempBufferSize))
|
||||
let tempData = malloc(tempSize)
|
||||
|
||||
defer {
|
||||
free(inData)
|
||||
free(outData)
|
||||
free(tempData)
|
||||
}
|
||||
|
||||
let source = CFDataGetBytePtr(providerData)
|
||||
memcpy(inBuffer.data, source, bytes)
|
||||
|
||||
for _ in 0..<iterations {
|
||||
vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, tempData, 0, 0, boxSize, boxSize, nil, vImage_Flags(kvImageEdgeExtend))
|
||||
|
||||
let temp = inBuffer.data
|
||||
inBuffer.data = outBuffer.data
|
||||
outBuffer.data = temp
|
||||
}
|
||||
|
||||
let context = colorSpace.flatMap {
|
||||
CGContext(data: inBuffer.data, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: $0, bitmapInfo: bitmapInfo.rawValue)
|
||||
}
|
||||
|
||||
return context?.makeImage(with: blendColor, blendMode: blendMode, size: size)
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// CaptureQuality.swift
|
||||
// DynamicBlurView
|
||||
//
|
||||
// Created by Kyohei Ito on 2017/08/17.
|
||||
// Copyright © 2017年 kyohei_ito. All rights reserved.
|
||||
//
|
||||
|
||||
public enum CaptureQuality {
|
||||
case `default`
|
||||
case low
|
||||
case medium
|
||||
case high
|
||||
|
||||
var imageScale: CGFloat {
|
||||
switch self {
|
||||
case .default, .high:
|
||||
return 0
|
||||
case .low, .medium:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
var interpolationQuality: CGInterpolationQuality {
|
||||
switch self {
|
||||
case .default, .low:
|
||||
return .none
|
||||
case .medium, .high:
|
||||
return .default
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// DynamicBlurView.h
|
||||
// DynamicBlurView
|
||||
//
|
||||
// Created by Kyohei Ito on 2015/04/08.
|
||||
// Copyright (c) 2015年 kyohei_ito. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
//! Project version number for DynamicBlurView.
|
||||
FOUNDATION_EXPORT double DynamicBlurViewVersionNumber;
|
||||
|
||||
//! Project version string for DynamicBlurView.
|
||||
FOUNDATION_EXPORT const unsigned char DynamicBlurViewVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <DynamicBlurView/PublicHeader.h>
|
||||
|
||||
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
//
|
||||
// DynamicBlurView.swift
|
||||
// DynamicBlurView
|
||||
//
|
||||
// Created by Kyohei Ito on 2015/04/08.
|
||||
// Copyright (c) 2015年 kyohei_ito. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
open class DynamicBlurView: UIView {
|
||||
open override class var layerClass : AnyClass {
|
||||
return BlurLayer.self
|
||||
}
|
||||
|
||||
private var staticImage: UIImage?
|
||||
private var displayLink: CADisplayLink?
|
||||
private var blurLayer: BlurLayer {
|
||||
return layer as! BlurLayer
|
||||
}
|
||||
private let mainQueue = DispatchQueue.main
|
||||
private let globalQueue: DispatchQueue = {
|
||||
if #available (iOS 8.0, *) {
|
||||
return .global(qos: .userInteractive)
|
||||
} else {
|
||||
return .global(priority: .high)
|
||||
}
|
||||
}()
|
||||
private var renderingTarget: UIView? {
|
||||
if isDeepRendering {
|
||||
return window
|
||||
} else {
|
||||
return superview
|
||||
}
|
||||
}
|
||||
|
||||
/// When true, it captures displays image and blur it asynchronously. Try to set true if needs more performance.
|
||||
/// Asynchronous drawing is possibly crash when needs to process on main thread that drawing with animation for example.
|
||||
open var drawsAsynchronously: Bool = false
|
||||
/// Radius of blur.
|
||||
open var blurRadius: CGFloat {
|
||||
set { blurLayer.blurRadius = newValue }
|
||||
get { return blurLayer.blurRadius }
|
||||
}
|
||||
/// Default is none.
|
||||
open var trackingMode: TrackingMode = .none {
|
||||
didSet {
|
||||
if trackingMode != oldValue {
|
||||
linkForDisplay()
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Blend color.
|
||||
open var blendColor: UIColor?
|
||||
/// Blend mode.
|
||||
open var blendMode: CGBlendMode = .plusLighter
|
||||
/// Default is 3.
|
||||
open var iterations: Int = 3
|
||||
/// If the view want to render beyond the layer, should be true.
|
||||
open var isDeepRendering: Bool = false
|
||||
/// When none of tracking mode, it can change the radius of blur with the ratio. Should set from 0 to 1.
|
||||
open var blurRatio: CGFloat = 1 {
|
||||
didSet {
|
||||
if let image = staticImage, oldValue != blurRatio {
|
||||
draw(image, blurRadius: blurRadius, fixes: false, baseLayer: renderingTarget?.layer)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Quality of captured image.
|
||||
open var quality: CaptureQuality = .medium
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
open override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
|
||||
if let view = renderingTarget, window != nil && trackingMode == .none {
|
||||
staticImage = snapshotImage(for: view.layer, conversion: !isDeepRendering)
|
||||
}
|
||||
}
|
||||
|
||||
open override func didMoveToSuperview() {
|
||||
super.didMoveToSuperview()
|
||||
|
||||
if superview == nil {
|
||||
displayLink?.invalidate()
|
||||
displayLink = nil
|
||||
} else {
|
||||
linkForDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
private func async(on queue: DispatchQueue, actions: @escaping () -> Void) {
|
||||
if drawsAsynchronously {
|
||||
queue.async(execute: actions)
|
||||
} else {
|
||||
actions()
|
||||
}
|
||||
}
|
||||
|
||||
private func sync(on queue: DispatchQueue, actions: () -> Void) {
|
||||
if drawsAsynchronously {
|
||||
queue.sync(execute: actions)
|
||||
} else {
|
||||
actions()
|
||||
}
|
||||
}
|
||||
|
||||
private func draw(_ image: UIImage, blurRadius radius: CGFloat, fixes isFixes: Bool, baseLayer: CALayer?) {
|
||||
async(on: globalQueue) { [weak self] in
|
||||
if let me = self, let blurredImage = image.blurred(radius: radius, iterations: me.iterations, ratio: me.blurRatio, blendColor: me.blendColor, blendMode: me.blendMode) {
|
||||
me.sync(on: me.mainQueue) {
|
||||
me.blurLayer.draw(blurredImage, fixes: isFixes, baseLayer: baseLayer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func blurLayerRect(to layer: CALayer, conversion: Bool) -> CGRect {
|
||||
if conversion {
|
||||
let presentationLayer = blurLayer.presentation() ?? blurLayer
|
||||
return presentationLayer.convert(presentationLayer.bounds, to: layer)
|
||||
} else {
|
||||
return layer.bounds
|
||||
}
|
||||
}
|
||||
|
||||
private func snapshotImage(for layer: CALayer, conversion: Bool) -> UIImage? {
|
||||
let rect = blurLayerRect(to: layer, conversion: conversion)
|
||||
guard let context = CGContext.imageContext(with: quality, rect: rect, opaque: isOpaque) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
blurLayer.render(in: context, for: layer)
|
||||
|
||||
defer {
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
|
||||
return UIGraphicsGetImageFromCurrentImageContext()
|
||||
}
|
||||
}
|
||||
|
||||
extension DynamicBlurView {
|
||||
open override func display(_ layer: CALayer) {
|
||||
let blurRadius = blurLayer.presentationRadius
|
||||
let isFixes = isDeepRendering && staticImage != nil
|
||||
if let view = renderingTarget, let image = staticImage ?? snapshotImage(for: view.layer, conversion: !isFixes) {
|
||||
draw(image, blurRadius: blurRadius, fixes: isFixes, baseLayer: view.layer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DynamicBlurView {
|
||||
private func linkForDisplay() {
|
||||
displayLink?.invalidate()
|
||||
displayLink = UIScreen.main.displayLink(withTarget: self, selector: #selector(DynamicBlurView.displayDidRefresh(_:)))
|
||||
displayLink?.add(to: .main, forMode: RunLoop.Mode(rawValue: trackingMode.description))
|
||||
}
|
||||
|
||||
@objc private func displayDidRefresh(_ displayLink: CADisplayLink) {
|
||||
display(layer)
|
||||
}
|
||||
}
|
||||
|
||||
extension DynamicBlurView {
|
||||
/// Remove cache of blur image then get it again.
|
||||
open func refresh() {
|
||||
blurLayer.refresh()
|
||||
staticImage = nil
|
||||
blurRatio = 1
|
||||
display(layer)
|
||||
}
|
||||
|
||||
/// Remove cache of blur image.
|
||||
open func remove() {
|
||||
blurLayer.refresh()
|
||||
staticImage = nil
|
||||
blurRatio = 1
|
||||
layer.contents = nil
|
||||
}
|
||||
|
||||
/// Should use when needs to change layout with animation when is set none of tracking mode.
|
||||
public func animate() {
|
||||
blurLayer.animate()
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// TrackingMode.swift
|
||||
// DynamicBlurView
|
||||
//
|
||||
// Created by Kyohei Ito on 2017/08/17.
|
||||
// Copyright © 2017年 kyohei_ito. All rights reserved.
|
||||
//
|
||||
|
||||
public enum TrackingMode: CustomStringConvertible {
|
||||
case tracking
|
||||
case common
|
||||
case none
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .tracking:
|
||||
return RunLoop.Mode.tracking.rawValue
|
||||
case .common:
|
||||
return RunLoop.Mode.common.rawValue
|
||||
case .none:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// UIImage+Blur.swift
|
||||
// DynamicBlurView
|
||||
//
|
||||
// Created by Kyohei Ito on 2017/08/11.
|
||||
// Copyright © 2017年 kyohei_ito. All rights reserved.
|
||||
//
|
||||
|
||||
public extension UIImage {
|
||||
func blurred(radius: CGFloat, iterations: Int, ratio: CGFloat, blendColor color: UIColor?, blendMode mode: CGBlendMode) -> UIImage? {
|
||||
guard let cgImage = cgImage else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cgImage.area <= 0 || radius <= 0 {
|
||||
return self
|
||||
}
|
||||
|
||||
var boxSize = UInt32(radius * scale * ratio)
|
||||
if boxSize % 2 == 0 {
|
||||
boxSize += 1
|
||||
}
|
||||
|
||||
return cgImage.blurred(with: boxSize, iterations: iterations, blendColor: color, blendMode: mode).map {
|
||||
UIImage(cgImage: $0, scale: scale, orientation: imageOrientation)
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// PopupDialogInteractiveTransition.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Handles interactive transition triggered via pan gesture recognizer on dialog
|
||||
final internal class InteractiveTransition: UIPercentDrivenInteractiveTransition {
|
||||
|
||||
// If the interactive transition was started
|
||||
var hasStarted = false
|
||||
|
||||
// If the interactive transition
|
||||
var shouldFinish = false
|
||||
|
||||
// The view controller containing the views
|
||||
// with attached gesture recognizers
|
||||
weak var viewController: UIViewController?
|
||||
|
||||
@objc func handlePan(_ sender: UIPanGestureRecognizer) {
|
||||
|
||||
guard let vc = viewController else { return }
|
||||
|
||||
guard let progress = calculateProgress(sender: sender) else { return }
|
||||
|
||||
switch sender.state {
|
||||
case .began:
|
||||
hasStarted = true
|
||||
vc.dismiss(animated: true, completion: nil)
|
||||
case .changed:
|
||||
shouldFinish = progress > 0.3
|
||||
update(progress)
|
||||
case .cancelled:
|
||||
hasStarted = false
|
||||
cancel()
|
||||
case .ended:
|
||||
hasStarted = false
|
||||
completionSpeed = 0.55
|
||||
shouldFinish ? finish() : cancel()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal extension InteractiveTransition {
|
||||
|
||||
/*!
|
||||
Translates the pan gesture recognizer position to the progress percentage
|
||||
- parameter sender: A UIPanGestureRecognizer
|
||||
- returns: Progress
|
||||
*/
|
||||
func calculateProgress(sender: UIPanGestureRecognizer) -> CGFloat? {
|
||||
guard let vc = viewController else { return nil }
|
||||
|
||||
// http://www.thorntech.com/2016/02/ios-tutorial-close-modal-dragging/
|
||||
let translation = sender.translation(in: vc.view)
|
||||
let verticalMovement = translation.y / vc.view.bounds.height
|
||||
let downwardMovement = fmaxf(Float(verticalMovement), 0.0)
|
||||
let downwardMovementPercent = fminf(downwardMovement, 1.0)
|
||||
let progress = CGFloat(downwardMovementPercent)
|
||||
|
||||
return progress
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// PopupDialog+Keyboard.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// This extension is designed to handle dialog positioning
|
||||
/// if a keyboard is displayed while the popup is on top
|
||||
internal extension PopupDialog {
|
||||
|
||||
// MARK: - Keyboard & orientation observers
|
||||
|
||||
/*! Add obserservers for UIKeyboard notifications */
|
||||
func addObservers() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged),
|
||||
name: UIDevice.orientationDidChangeNotification,
|
||||
object: nil)
|
||||
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(keyboardWillShow),
|
||||
name: UIResponder.keyboardWillShowNotification,
|
||||
object: nil)
|
||||
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(keyboardWillHide),
|
||||
name: UIResponder.keyboardWillHideNotification,
|
||||
object: nil)
|
||||
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(keyboardWillChangeFrame),
|
||||
name: UIResponder.keyboardWillChangeFrameNotification,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
/*! Remove observers */
|
||||
func removeObservers() {
|
||||
NotificationCenter.default.removeObserver(self,
|
||||
name: UIDevice.orientationDidChangeNotification,
|
||||
object: nil)
|
||||
|
||||
NotificationCenter.default.removeObserver(self,
|
||||
name: UIResponder.keyboardWillShowNotification,
|
||||
object: nil)
|
||||
|
||||
NotificationCenter.default.removeObserver(self,
|
||||
name: UIResponder.keyboardWillHideNotification,
|
||||
object: nil)
|
||||
|
||||
NotificationCenter.default.removeObserver(self,
|
||||
name: UIResponder.keyboardWillChangeFrameNotification,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
/*!
|
||||
Keyboard will show notification listener
|
||||
- parameter notification: NSNotification
|
||||
*/
|
||||
@objc fileprivate func keyboardWillShow(_ notification: Notification) {
|
||||
guard isTopAndVisible else { return }
|
||||
keyboardShown = true
|
||||
centerPopup()
|
||||
}
|
||||
|
||||
/*!
|
||||
Keyboard will hide notification listener
|
||||
- parameter notification: NSNotification
|
||||
*/
|
||||
@objc fileprivate func keyboardWillHide(_ notification: Notification) {
|
||||
guard isTopAndVisible else { return }
|
||||
keyboardShown = false
|
||||
centerPopup()
|
||||
}
|
||||
|
||||
/*!
|
||||
Keyboard will change frame notification listener
|
||||
- parameter notification: NSNotification
|
||||
*/
|
||||
@objc fileprivate func keyboardWillChangeFrame(_ notification: Notification) {
|
||||
guard let keyboardRect = (notification as NSNotification).userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else {
|
||||
return
|
||||
}
|
||||
keyboardHeight = keyboardRect.cgRectValue.height
|
||||
}
|
||||
|
||||
/*!
|
||||
Listen to orientation changes
|
||||
- parameter notification: NSNotification
|
||||
*/
|
||||
@objc fileprivate func orientationChanged(_ notification: Notification) {
|
||||
if keyboardShown { centerPopup() }
|
||||
}
|
||||
|
||||
fileprivate func centerPopup() {
|
||||
|
||||
// Make sure keyboard should reposition on keayboard notifications
|
||||
guard keyboardShiftsView else { return }
|
||||
|
||||
// Make sure a valid keyboard height is available
|
||||
guard let keyboardHeight = keyboardHeight else { return }
|
||||
|
||||
// Calculate new center of shadow background
|
||||
let popupCenter = keyboardShown ? keyboardHeight / -2 : 0
|
||||
|
||||
// Reposition and animate
|
||||
popupContainerView.centerYConstraint?.constant = popupCenter
|
||||
popupContainerView.pv_layoutIfNeededAnimated()
|
||||
}
|
||||
}
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
//
|
||||
// PopupDialog.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// Creates a Popup dialog similar to UIAlertController
|
||||
final public class PopupDialog: UIViewController {
|
||||
|
||||
// MARK: Private / Internal
|
||||
|
||||
/// First init flag
|
||||
fileprivate var initialized = false
|
||||
|
||||
/// StatusBar display related
|
||||
fileprivate let hideStatusBar: Bool
|
||||
fileprivate var statusBarShouldBeHidden: Bool = false
|
||||
|
||||
/// Width for iPad displays
|
||||
fileprivate let preferredWidth: CGFloat
|
||||
|
||||
/// The completion handler
|
||||
fileprivate var completion: (() -> Void)?
|
||||
|
||||
/// The custom transition presentation manager
|
||||
fileprivate var presentationManager: PresentationManager!
|
||||
|
||||
/// Interactor class for pan gesture dismissal
|
||||
fileprivate lazy var interactor = InteractiveTransition()
|
||||
|
||||
/// Returns the controllers view
|
||||
internal var popupContainerView: PopupDialogContainerView {
|
||||
return view as! PopupDialogContainerView // swiftlint:disable:this force_cast
|
||||
}
|
||||
|
||||
/// The set of buttons
|
||||
fileprivate var buttons = [PopupDialogButton]()
|
||||
|
||||
/// Whether keyboard has shifted view
|
||||
internal var keyboardShown = false
|
||||
|
||||
/// Keyboard height
|
||||
internal var keyboardHeight: CGFloat?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/// The content view of the popup dialog
|
||||
public var viewController: UIViewController
|
||||
|
||||
/// Whether or not to shift view for keyboard display
|
||||
public var keyboardShiftsView = true
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
/*!
|
||||
Creates a standard popup dialog with title, message and image field
|
||||
|
||||
- parameter title: The dialog title
|
||||
- parameter message: The dialog message
|
||||
- parameter image: The dialog image
|
||||
- parameter buttonAlignment: The dialog button alignment
|
||||
- parameter transitionStyle: The dialog transition style
|
||||
- parameter preferredWidth: The preferred width for iPad screens
|
||||
- parameter tapGestureDismissal: Indicates if dialog can be dismissed via tap gesture
|
||||
- parameter panGestureDismissal: Indicates if dialog can be dismissed via pan gesture
|
||||
- parameter hideStatusBar: Whether to hide the status bar on PopupDialog presentation
|
||||
- parameter completion: Completion block invoked when dialog was dismissed
|
||||
|
||||
- returns: Popup dialog default style
|
||||
*/
|
||||
@objc public convenience init(
|
||||
title: String?,
|
||||
message: String?,
|
||||
image: UIImage? = nil,
|
||||
buttonAlignment: NSLayoutConstraint.Axis = .vertical,
|
||||
transitionStyle: PopupDialogTransitionStyle = .bounceUp,
|
||||
preferredWidth: CGFloat = 340,
|
||||
tapGestureDismissal: Bool = true,
|
||||
panGestureDismissal: Bool = true,
|
||||
hideStatusBar: Bool = false,
|
||||
completion: (() -> Void)? = nil) {
|
||||
|
||||
// Create and configure the standard popup dialog view
|
||||
let viewController = PopupDialogDefaultViewController()
|
||||
viewController.titleText = title
|
||||
viewController.messageText = message
|
||||
viewController.image = image
|
||||
|
||||
// Call designated initializer
|
||||
self.init(viewController: viewController,
|
||||
buttonAlignment: buttonAlignment,
|
||||
transitionStyle: transitionStyle,
|
||||
preferredWidth: preferredWidth,
|
||||
tapGestureDismissal: tapGestureDismissal,
|
||||
panGestureDismissal: panGestureDismissal,
|
||||
hideStatusBar: hideStatusBar,
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a popup dialog containing a custom view
|
||||
|
||||
- parameter viewController: A custom view controller to be displayed
|
||||
- parameter buttonAlignment: The dialog button alignment
|
||||
- parameter transitionStyle: The dialog transition style
|
||||
- parameter preferredWidth: The preferred width for iPad screens
|
||||
- parameter tapGestureDismissal: Indicates if dialog can be dismissed via tap gesture
|
||||
- parameter panGestureDismissal: Indicates if dialog can be dismissed via pan gesture
|
||||
- parameter hideStatusBar: Whether to hide the status bar on PopupDialog presentation
|
||||
- parameter completion: Completion block invoked when dialog was dismissed
|
||||
|
||||
- returns: Popup dialog with a custom view controller
|
||||
*/
|
||||
@objc public init(
|
||||
viewController: UIViewController,
|
||||
buttonAlignment: NSLayoutConstraint.Axis = .vertical,
|
||||
transitionStyle: PopupDialogTransitionStyle = .bounceUp,
|
||||
preferredWidth: CGFloat = 340,
|
||||
tapGestureDismissal: Bool = true,
|
||||
panGestureDismissal: Bool = true,
|
||||
hideStatusBar: Bool = false,
|
||||
completion: (() -> Void)? = nil) {
|
||||
|
||||
self.viewController = viewController
|
||||
self.preferredWidth = preferredWidth
|
||||
self.hideStatusBar = hideStatusBar
|
||||
self.completion = completion
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
// Init the presentation manager
|
||||
presentationManager = PresentationManager(transitionStyle: transitionStyle, interactor: interactor)
|
||||
|
||||
// Assign the interactor view controller
|
||||
interactor.viewController = self
|
||||
|
||||
// Define presentation styles
|
||||
transitioningDelegate = presentationManager
|
||||
modalPresentationStyle = .custom
|
||||
|
||||
// StatusBar setup
|
||||
modalPresentationCapturesStatusBarAppearance = true
|
||||
|
||||
// Add our custom view to the container
|
||||
addChild(viewController)
|
||||
popupContainerView.stackView.insertArrangedSubview(viewController.view, at: 0)
|
||||
popupContainerView.buttonStackView.axis = buttonAlignment
|
||||
viewController.didMove(toParent: self)
|
||||
|
||||
// Allow for dialog dismissal on background tap
|
||||
if tapGestureDismissal {
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
|
||||
tapRecognizer.cancelsTouchesInView = false
|
||||
popupContainerView.addGestureRecognizer(tapRecognizer)
|
||||
}
|
||||
// Allow for dialog dismissal on dialog pan gesture
|
||||
if panGestureDismissal {
|
||||
let panRecognizer = UIPanGestureRecognizer(target: interactor, action: #selector(InteractiveTransition.handlePan))
|
||||
panRecognizer.cancelsTouchesInView = false
|
||||
popupContainerView.stackView.addGestureRecognizer(panRecognizer)
|
||||
}
|
||||
}
|
||||
|
||||
// Init with coder not implemented
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: - View life cycle
|
||||
|
||||
/// Replaces controller view with popup view
|
||||
public override func loadView() {
|
||||
view = PopupDialogContainerView(frame: UIScreen.main.bounds, preferredWidth: preferredWidth)
|
||||
}
|
||||
|
||||
public override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
addObservers()
|
||||
|
||||
guard !initialized else { return }
|
||||
appendButtons()
|
||||
initialized = true
|
||||
}
|
||||
|
||||
public override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
statusBarShouldBeHidden = hideStatusBar
|
||||
UIView.animate(withDuration: 0.15) {
|
||||
self.setNeedsStatusBarAppearanceUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
public override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
removeObservers()
|
||||
}
|
||||
|
||||
deinit {
|
||||
completion?()
|
||||
completion = nil
|
||||
}
|
||||
|
||||
// MARK: - Dismissal related
|
||||
|
||||
@objc fileprivate func handleTap(_ sender: UITapGestureRecognizer) {
|
||||
|
||||
// Make sure it's not a tap on the dialog but the background
|
||||
let point = sender.location(in: popupContainerView.stackView)
|
||||
guard !popupContainerView.stackView.point(inside: point, with: nil) else { return }
|
||||
dismiss()
|
||||
}
|
||||
|
||||
/*!
|
||||
Dismisses the popup dialog
|
||||
*/
|
||||
@objc public func dismiss(_ completion: (() -> Void)? = nil) {
|
||||
self.dismiss(animated: true) {
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Button related
|
||||
|
||||
/*!
|
||||
Appends the buttons added to the popup dialog
|
||||
to the placeholder stack view
|
||||
*/
|
||||
fileprivate func appendButtons() {
|
||||
|
||||
// Add action to buttons
|
||||
let stackView = popupContainerView.stackView
|
||||
let buttonStackView = popupContainerView.buttonStackView
|
||||
if buttons.isEmpty {
|
||||
stackView.removeArrangedSubview(popupContainerView.buttonStackView)
|
||||
}
|
||||
|
||||
for (index, button) in buttons.enumerated() {
|
||||
button.needsLeftSeparator = buttonStackView.axis == .horizontal && index > 0
|
||||
buttonStackView.addArrangedSubview(button)
|
||||
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds a single PopupDialogButton to the Popup dialog
|
||||
- parameter button: A PopupDialogButton instance
|
||||
*/
|
||||
@objc public func addButton(_ button: PopupDialogButton) {
|
||||
buttons.append(button)
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds an array of PopupDialogButtons to the Popup dialog
|
||||
- parameter buttons: A list of PopupDialogButton instances
|
||||
*/
|
||||
@objc public func addButtons(_ buttons: [PopupDialogButton]) {
|
||||
self.buttons += buttons
|
||||
}
|
||||
|
||||
/// Calls the action closure of the button instance tapped
|
||||
@objc fileprivate func buttonTapped(_ button: PopupDialogButton) {
|
||||
if button.dismissOnTap {
|
||||
dismiss({ button.buttonAction?() })
|
||||
} else {
|
||||
button.buttonAction?()
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Simulates a button tap for the given index
|
||||
Makes testing a breeze
|
||||
- parameter index: The index of the button to tap
|
||||
*/
|
||||
public func tapButtonWithIndex(_ index: Int) {
|
||||
let button = buttons[index]
|
||||
button.buttonAction?()
|
||||
}
|
||||
|
||||
// MARK: - StatusBar display related
|
||||
|
||||
public override var prefersStatusBarHidden: Bool {
|
||||
return statusBarShouldBeHidden
|
||||
}
|
||||
|
||||
public override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
||||
return .slide
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - View proxy values
|
||||
|
||||
extension PopupDialog {
|
||||
|
||||
/// The button alignment of the alert dialog
|
||||
@objc public var buttonAlignment: NSLayoutConstraint.Axis {
|
||||
get {
|
||||
return popupContainerView.buttonStackView.axis
|
||||
}
|
||||
set {
|
||||
popupContainerView.buttonStackView .axis = newValue
|
||||
popupContainerView.pv_layoutIfNeededAnimated()
|
||||
}
|
||||
}
|
||||
|
||||
/// The transition style
|
||||
@objc public var transitionStyle: PopupDialogTransitionStyle {
|
||||
get { return presentationManager.transitionStyle }
|
||||
set { presentationManager.transitionStyle = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Shake
|
||||
|
||||
extension PopupDialog {
|
||||
|
||||
/// Performs a shake animation on the dialog
|
||||
@objc public func shake() {
|
||||
popupContainerView.pv_shake()
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// PopupDialogButton.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// Represents the default button for the popup dialog
|
||||
open class PopupDialogButton: UIButton {
|
||||
|
||||
public typealias PopupDialogButtonAction = () -> Void
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/// The font and size of the button title
|
||||
@objc open dynamic var titleFont: UIFont? {
|
||||
get { return titleLabel?.font }
|
||||
set { titleLabel?.font = newValue }
|
||||
}
|
||||
|
||||
/// The height of the button
|
||||
@objc open dynamic var buttonHeight: Int
|
||||
|
||||
/// The title color of the button
|
||||
@objc open dynamic var titleColor: UIColor? {
|
||||
get { return self.titleColor(for: UIControl.State()) }
|
||||
set { setTitleColor(newValue, for: UIControl.State()) }
|
||||
}
|
||||
|
||||
/// The background color of the button
|
||||
@objc open dynamic var buttonColor: UIColor? {
|
||||
get { return backgroundColor }
|
||||
set { backgroundColor = newValue }
|
||||
}
|
||||
|
||||
/// The separator color of this button
|
||||
@objc open dynamic var separatorColor: UIColor? {
|
||||
get { return separator.backgroundColor }
|
||||
set {
|
||||
separator.backgroundColor = newValue
|
||||
leftSeparator.backgroundColor = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Default appearance of the button
|
||||
open var defaultTitleFont = UIFont.systemFont(ofSize: 14)
|
||||
open var defaultTitleColor = UIColor(red: 0.25, green: 0.53, blue: 0.91, alpha: 1)
|
||||
open var defaultButtonColor = UIColor.clear
|
||||
open var defaultSeparatorColor = UIColor(white: 0.9, alpha: 1)
|
||||
|
||||
/// Whether button should dismiss popup when tapped
|
||||
@objc open var dismissOnTap = true
|
||||
|
||||
/// The action called when the button is tapped
|
||||
open fileprivate(set) var buttonAction: PopupDialogButtonAction?
|
||||
|
||||
// MARK: Private
|
||||
|
||||
fileprivate lazy var separator: UIView = {
|
||||
let line = UIView(frame: .zero)
|
||||
line.translatesAutoresizingMaskIntoConstraints = false
|
||||
return line
|
||||
}()
|
||||
|
||||
fileprivate lazy var leftSeparator: UIView = {
|
||||
let line = UIView(frame: .zero)
|
||||
line.translatesAutoresizingMaskIntoConstraints = false
|
||||
line.alpha = 0
|
||||
return line
|
||||
}()
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal var needsLeftSeparator: Bool = false {
|
||||
didSet {
|
||||
leftSeparator.alpha = needsLeftSeparator ? 1.0 : 0.0
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
/*!
|
||||
Creates a button that can be added to the popup dialog
|
||||
|
||||
- parameter title: The button title
|
||||
- parameter dismisssOnTap: Whether a tap automatically dismisses the dialog
|
||||
- parameter action: The action closure
|
||||
|
||||
- returns: PopupDialogButton
|
||||
*/
|
||||
@objc public init(title: String, height: Int = 45, dismissOnTap: Bool = true, action: PopupDialogButtonAction?) {
|
||||
|
||||
// Assign the button height
|
||||
buttonHeight = height
|
||||
|
||||
// Assign the button action
|
||||
buttonAction = action
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
// Set the button title
|
||||
setTitle(title, for: UIControl.State())
|
||||
|
||||
self.dismissOnTap = dismissOnTap
|
||||
|
||||
// Setup the views
|
||||
setupView()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: View setup
|
||||
|
||||
open func setupView() {
|
||||
|
||||
// Default appearance
|
||||
setTitleColor(defaultTitleColor, for: UIControl.State())
|
||||
titleLabel?.font = defaultTitleFont
|
||||
backgroundColor = defaultButtonColor
|
||||
separator.backgroundColor = defaultSeparatorColor
|
||||
leftSeparator.backgroundColor = defaultSeparatorColor
|
||||
|
||||
// Add and layout views
|
||||
addSubview(separator)
|
||||
addSubview(leftSeparator)
|
||||
|
||||
let views = ["separator": separator, "leftSeparator": leftSeparator, "button": self]
|
||||
let metrics = ["buttonHeight": buttonHeight]
|
||||
var constraints = [NSLayoutConstraint]()
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:[button(buttonHeight)]", options: [], metrics: metrics, views: views)
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[separator]|", options: [], metrics: nil, views: views)
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[separator(1)]", options: [], metrics: nil, views: views)
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[leftSeparator(1)]", options: [], metrics: nil, views: views)
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[leftSeparator]|", options: [], metrics: nil, views: views)
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
|
||||
open override var isHighlighted: Bool {
|
||||
didSet {
|
||||
isHighlighted ? pv_fade(.out, 0.5) : pv_fade(.in, 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
//
|
||||
// PopupDialogContainerView.swift
|
||||
// Pods
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// The main view of the popup dialog
|
||||
final public class PopupDialogContainerView: UIView {
|
||||
|
||||
// MARK: - Appearance
|
||||
|
||||
/// The background color of the popup dialog
|
||||
override public dynamic var backgroundColor: UIColor? {
|
||||
get { return container.backgroundColor }
|
||||
set { container.backgroundColor = newValue }
|
||||
}
|
||||
|
||||
/// The corner radius of the popup view
|
||||
@objc public dynamic var cornerRadius: Float {
|
||||
get { return Float(shadowContainer.layer.cornerRadius) }
|
||||
set {
|
||||
let radius = CGFloat(newValue)
|
||||
shadowContainer.layer.cornerRadius = radius
|
||||
container.layer.cornerRadius = radius
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Shadow related
|
||||
|
||||
/// Enable / disable shadow rendering of the container
|
||||
@objc public dynamic var shadowEnabled: Bool {
|
||||
get { return shadowContainer.layer.shadowRadius > 0 }
|
||||
set { shadowContainer.layer.shadowRadius = newValue ? shadowRadius : 0 }
|
||||
}
|
||||
|
||||
/// Color of the container shadow
|
||||
@objc public dynamic var shadowColor: UIColor? {
|
||||
get {
|
||||
guard let color = shadowContainer.layer.shadowColor else {
|
||||
return nil
|
||||
}
|
||||
return UIColor(cgColor: color)
|
||||
}
|
||||
set { shadowContainer.layer.shadowColor = newValue?.cgColor }
|
||||
}
|
||||
|
||||
/// Radius of the container shadow
|
||||
@objc public dynamic var shadowRadius: CGFloat {
|
||||
get { return shadowContainer.layer.shadowRadius }
|
||||
set { shadowContainer.layer.shadowRadius = newValue }
|
||||
}
|
||||
|
||||
/// Opacity of the the container shadow
|
||||
@objc public dynamic var shadowOpacity: Float {
|
||||
get { return shadowContainer.layer.shadowOpacity }
|
||||
set { shadowContainer.layer.shadowOpacity = newValue }
|
||||
}
|
||||
|
||||
/// Offset of the the container shadow
|
||||
@objc public dynamic var shadowOffset: CGSize {
|
||||
get { return shadowContainer.layer.shadowOffset }
|
||||
set { shadowContainer.layer.shadowOffset = newValue }
|
||||
}
|
||||
|
||||
/// Path of the the container shadow
|
||||
@objc public dynamic var shadowPath: CGPath? {
|
||||
get { return shadowContainer.layer.shadowPath}
|
||||
set { shadowContainer.layer.shadowPath = newValue }
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
/// The shadow container is the basic view of the PopupDialog
|
||||
/// As it does not clip subviews, a shadow can be applied to it
|
||||
internal lazy var shadowContainer: UIView = {
|
||||
let shadowContainer = UIView(frame: .zero)
|
||||
shadowContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
shadowContainer.backgroundColor = UIColor.clear
|
||||
shadowContainer.layer.shadowColor = UIColor.black.cgColor
|
||||
shadowContainer.layer.shadowRadius = 5
|
||||
shadowContainer.layer.shadowOpacity = 0.4
|
||||
shadowContainer.layer.shadowOffset = CGSize(width: 0, height: 0)
|
||||
shadowContainer.layer.cornerRadius = 4
|
||||
return shadowContainer
|
||||
}()
|
||||
|
||||
/// The container view is a child of shadowContainer and contains
|
||||
/// all other views. It clips to bounds so cornerRadius can be set
|
||||
internal lazy var container: UIView = {
|
||||
let container = UIView(frame: .zero)
|
||||
container.translatesAutoresizingMaskIntoConstraints = false
|
||||
container.backgroundColor = UIColor.white
|
||||
container.clipsToBounds = true
|
||||
container.layer.cornerRadius = 4
|
||||
return container
|
||||
}()
|
||||
|
||||
// The container stack view for buttons
|
||||
internal lazy var buttonStackView: UIStackView = {
|
||||
let buttonStackView = UIStackView()
|
||||
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
buttonStackView.distribution = .fillEqually
|
||||
buttonStackView.spacing = 0
|
||||
return buttonStackView
|
||||
}()
|
||||
|
||||
// The main stack view, containing all relevant views
|
||||
internal lazy var stackView: UIStackView = {
|
||||
let stackView = UIStackView(arrangedSubviews: [self.buttonStackView])
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .vertical
|
||||
stackView.spacing = 0
|
||||
return stackView
|
||||
}()
|
||||
|
||||
// The preferred width for iPads
|
||||
fileprivate let preferredWidth: CGFloat
|
||||
|
||||
// MARK: - Constraints
|
||||
|
||||
/// The center constraint of the shadow container
|
||||
internal var centerYConstraint: NSLayoutConstraint?
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
internal init(frame: CGRect, preferredWidth: CGFloat) {
|
||||
self.preferredWidth = preferredWidth
|
||||
super.init(frame: frame)
|
||||
setupViews()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: - View setup
|
||||
|
||||
internal func setupViews() {
|
||||
|
||||
// Add views
|
||||
addSubview(shadowContainer)
|
||||
shadowContainer.addSubview(container)
|
||||
container.addSubview(stackView)
|
||||
|
||||
// Layout views
|
||||
let views = ["shadowContainer": shadowContainer, "container": container, "stackView": stackView]
|
||||
var constraints = [NSLayoutConstraint]()
|
||||
|
||||
// Shadow container constraints
|
||||
if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad {
|
||||
let metrics = ["preferredWidth": preferredWidth]
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-(>=40)-[shadowContainer(==preferredWidth@900)]-(>=40)-|", options: [], metrics: metrics, views: views)
|
||||
} else {
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-(>=10,==20@900)-[shadowContainer(<=340,>=300)]-(>=10,==20@900)-|", options: [], metrics: nil, views: views)
|
||||
}
|
||||
constraints += [NSLayoutConstraint(item: shadowContainer, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)]
|
||||
centerYConstraint = NSLayoutConstraint(item: shadowContainer, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0)
|
||||
|
||||
if let centerYConstraint = centerYConstraint {
|
||||
constraints.append(centerYConstraint)
|
||||
}
|
||||
|
||||
// Container constraints
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[container]|", options: [], metrics: nil, views: views)
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[container]|", options: [], metrics: nil, views: views)
|
||||
|
||||
// Main stack view constraints
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[stackView]|", options: [], metrics: nil, views: views)
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[stackView]|", options: [], metrics: nil, views: views)
|
||||
|
||||
// Activate constraints
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// PopupDialogDefaultButtons.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
// MARK: Default button
|
||||
|
||||
/// Represents the default button for the popup dialog
|
||||
public final class DefaultButton: PopupDialogButton {}
|
||||
|
||||
// MARK: Cancel button
|
||||
|
||||
/// Represents a cancel button for the popup dialog
|
||||
public final class CancelButton: PopupDialogButton {
|
||||
|
||||
override public func setupView() {
|
||||
defaultTitleColor = UIColor.lightGray
|
||||
super.setupView()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: destructive button
|
||||
|
||||
/// Represents a destructive button for the popup dialog
|
||||
public final class DestructiveButton: PopupDialogButton {
|
||||
|
||||
override public func setupView() {
|
||||
defaultTitleColor = UIColor.red
|
||||
super.setupView()
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// PopupDialogView.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// The main view of the popup dialog
|
||||
final public class PopupDialogDefaultView: UIView {
|
||||
|
||||
// MARK: - Appearance
|
||||
|
||||
/// The font and size of the title label
|
||||
@objc public dynamic var titleFont: UIFont {
|
||||
get { return titleLabel.font }
|
||||
set { titleLabel.font = newValue }
|
||||
}
|
||||
|
||||
/// The color of the title label
|
||||
@objc public dynamic var titleColor: UIColor? {
|
||||
get { return titleLabel.textColor }
|
||||
set { titleLabel.textColor = newValue }
|
||||
}
|
||||
|
||||
/// The text alignment of the title label
|
||||
@objc public dynamic var titleTextAlignment: NSTextAlignment {
|
||||
get { return titleLabel.textAlignment }
|
||||
set { titleLabel.textAlignment = newValue }
|
||||
}
|
||||
|
||||
/// The font and size of the body label
|
||||
@objc public dynamic var messageFont: UIFont {
|
||||
get { return messageLabel.font }
|
||||
set { messageLabel.font = newValue }
|
||||
}
|
||||
|
||||
/// The color of the message label
|
||||
@objc public dynamic var messageColor: UIColor? {
|
||||
get { return messageLabel.textColor }
|
||||
set { messageLabel.textColor = newValue}
|
||||
}
|
||||
|
||||
/// The text alignment of the message label
|
||||
@objc public dynamic var messageTextAlignment: NSTextAlignment {
|
||||
get { return messageLabel.textAlignment }
|
||||
set { messageLabel.textAlignment = newValue }
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
/// The view that will contain the image, if set
|
||||
internal lazy var imageView: UIImageView = {
|
||||
let imageView = UIImageView(frame: .zero)
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.clipsToBounds = true
|
||||
return imageView
|
||||
}()
|
||||
|
||||
/// The title label of the dialog
|
||||
internal lazy var titleLabel: UILabel = {
|
||||
let titleLabel = UILabel(frame: .zero)
|
||||
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
titleLabel.numberOfLines = 0
|
||||
titleLabel.textAlignment = .center
|
||||
titleLabel.textColor = UIColor(white: 0.4, alpha: 1)
|
||||
titleLabel.font = .boldSystemFont(ofSize: 14)
|
||||
return titleLabel
|
||||
}()
|
||||
|
||||
/// The message label of the dialog
|
||||
internal lazy var messageLabel: UILabel = {
|
||||
let messageLabel = UILabel(frame: .zero)
|
||||
messageLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
messageLabel.numberOfLines = 0
|
||||
messageLabel.textAlignment = .center
|
||||
messageLabel.textColor = UIColor(white: 0.6, alpha: 1)
|
||||
messageLabel.font = .systemFont(ofSize: 14)
|
||||
return messageLabel
|
||||
}()
|
||||
|
||||
/// The height constraint of the image view, 0 by default
|
||||
internal var imageHeightConstraint: NSLayoutConstraint?
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
internal override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupViews()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: - View setup
|
||||
|
||||
internal func setupViews() {
|
||||
|
||||
// Self setup
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
// Add views
|
||||
addSubview(imageView)
|
||||
addSubview(titleLabel)
|
||||
addSubview(messageLabel)
|
||||
|
||||
// Layout views
|
||||
let views = ["imageView": imageView, "titleLabel": titleLabel, "messageLabel": messageLabel] as [String: Any]
|
||||
var constraints = [NSLayoutConstraint]()
|
||||
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[imageView]|", options: [], metrics: nil, views: views)
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-(==20@900)-[titleLabel]-(==20@900)-|", options: [], metrics: nil, views: views)
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-(==20@900)-[messageLabel]-(==20@900)-|", options: [], metrics: nil, views: views)
|
||||
constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[imageView]-(==30@900)-[titleLabel]-(==8@900)-[messageLabel]-(==30@900)-|", options: [], metrics: nil, views: views)
|
||||
|
||||
// ImageView height constraint
|
||||
imageHeightConstraint = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: imageView, attribute: .height, multiplier: 0, constant: 0)
|
||||
|
||||
if let imageHeightConstraint = imageHeightConstraint {
|
||||
constraints.append(imageHeightConstraint)
|
||||
}
|
||||
|
||||
// Activate constraints
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
//
|
||||
// PopupDialogDefaultViewController.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
final public class PopupDialogDefaultViewController: UIViewController {
|
||||
|
||||
public var standardView: PopupDialogDefaultView {
|
||||
return view as! PopupDialogDefaultView // swiftlint:disable:this force_cast
|
||||
}
|
||||
|
||||
override public func loadView() {
|
||||
super.loadView()
|
||||
view = PopupDialogDefaultView(frame: .zero)
|
||||
}
|
||||
}
|
||||
|
||||
public extension PopupDialogDefaultViewController {
|
||||
|
||||
// MARK: - Setter / Getter
|
||||
|
||||
// MARK: Content
|
||||
|
||||
/// The dialog image
|
||||
var image: UIImage? {
|
||||
get { return standardView.imageView.image }
|
||||
set {
|
||||
standardView.imageView.image = newValue
|
||||
standardView.imageHeightConstraint?.constant = standardView.imageView.pv_heightForImageView()
|
||||
}
|
||||
}
|
||||
|
||||
/// The title text of the dialog
|
||||
var titleText: String? {
|
||||
get { return standardView.titleLabel.text }
|
||||
set {
|
||||
standardView.titleLabel.text = newValue
|
||||
standardView.pv_layoutIfNeededAnimated()
|
||||
}
|
||||
}
|
||||
|
||||
/// The message text of the dialog
|
||||
var messageText: String? {
|
||||
get { return standardView.messageLabel.text }
|
||||
set {
|
||||
standardView.messageLabel.text = newValue
|
||||
standardView.pv_layoutIfNeededAnimated()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Appearance
|
||||
|
||||
/// The font and size of the title label
|
||||
@objc dynamic var titleFont: UIFont {
|
||||
get { return standardView.titleFont }
|
||||
set {
|
||||
standardView.titleFont = newValue
|
||||
standardView.pv_layoutIfNeededAnimated()
|
||||
}
|
||||
}
|
||||
|
||||
/// The color of the title label
|
||||
@objc dynamic var titleColor: UIColor? {
|
||||
get { return standardView.titleLabel.textColor }
|
||||
set {
|
||||
standardView.titleColor = newValue
|
||||
standardView.pv_layoutIfNeededAnimated()
|
||||
}
|
||||
}
|
||||
|
||||
/// The text alignment of the title label
|
||||
@objc dynamic var titleTextAlignment: NSTextAlignment {
|
||||
get { return standardView.titleTextAlignment }
|
||||
set {
|
||||
standardView.titleTextAlignment = newValue
|
||||
standardView.pv_layoutIfNeededAnimated()
|
||||
}
|
||||
}
|
||||
|
||||
/// The font and size of the body label
|
||||
@objc dynamic var messageFont: UIFont {
|
||||
get { return standardView.messageFont}
|
||||
set {
|
||||
standardView.messageFont = newValue
|
||||
standardView.pv_layoutIfNeededAnimated()
|
||||
}
|
||||
}
|
||||
|
||||
/// The color of the message label
|
||||
@objc dynamic var messageColor: UIColor? {
|
||||
get { return standardView.messageColor }
|
||||
set {
|
||||
standardView.messageColor = newValue
|
||||
standardView.pv_layoutIfNeededAnimated()
|
||||
}
|
||||
}
|
||||
|
||||
/// The text alignment of the message label
|
||||
@objc dynamic var messageTextAlignment: NSTextAlignment {
|
||||
get { return standardView.messageTextAlignment }
|
||||
set {
|
||||
standardView.messageTextAlignment = newValue
|
||||
standardView.pv_layoutIfNeededAnimated()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
standardView.imageHeightConstraint?.constant = standardView.imageView.pv_heightForImageView()
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// PopupDialogOverlayView.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// The (blurred) overlay view below the popup dialog
|
||||
final public class PopupDialogOverlayView: UIView {
|
||||
|
||||
// MARK: - Appearance
|
||||
|
||||
/// Turns the blur of the overlay view on or off
|
||||
@objc public dynamic var blurEnabled: Bool {
|
||||
get { return !blurView.isHidden }
|
||||
set { blurView.isHidden = !newValue }
|
||||
}
|
||||
|
||||
/// The blur radius of the overlay view
|
||||
@objc public dynamic var blurRadius: CGFloat {
|
||||
get { return blurView.blurRadius }
|
||||
set { blurView.blurRadius = newValue }
|
||||
}
|
||||
|
||||
/// Whether the blur view should allow for
|
||||
/// live rendering of the background
|
||||
@objc public dynamic var liveBlurEnabled: Bool {
|
||||
get { return blurView.trackingMode == .common }
|
||||
set {
|
||||
if newValue {
|
||||
blurView.trackingMode = .common
|
||||
} else {
|
||||
blurView.trackingMode = .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The background color of the overlay view
|
||||
@objc public dynamic var color: UIColor? {
|
||||
get { return overlay.backgroundColor }
|
||||
set { overlay.backgroundColor = newValue }
|
||||
}
|
||||
|
||||
/// The opacity of the overlay view
|
||||
@objc public dynamic var opacity: CGFloat {
|
||||
get { return overlay.alpha }
|
||||
set { overlay.alpha = newValue }
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
internal lazy var blurView: DynamicBlurView = {
|
||||
let blurView = DynamicBlurView(frame: .zero)
|
||||
blurView.blurRadius = 8
|
||||
blurView.trackingMode = .none
|
||||
blurView.isDeepRendering = true
|
||||
blurView.tintColor = .clear
|
||||
blurView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
|
||||
return blurView
|
||||
}()
|
||||
|
||||
internal lazy var overlay: UIView = {
|
||||
let overlay = UIView(frame: .zero)
|
||||
overlay.backgroundColor = .black
|
||||
overlay.alpha = 0.7
|
||||
overlay.autoresizingMask = [.flexibleHeight, .flexibleWidth]
|
||||
return overlay
|
||||
}()
|
||||
|
||||
// MARK: - Inititalizers
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupView()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: - View setup
|
||||
|
||||
fileprivate func setupView() {
|
||||
|
||||
autoresizingMask = [.flexibleHeight, .flexibleWidth]
|
||||
backgroundColor = .clear
|
||||
alpha = 0
|
||||
|
||||
addSubview(blurView)
|
||||
addSubview(overlay)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Deprecated
|
||||
|
||||
extension PopupDialogOverlayView {
|
||||
|
||||
/// Whether the blur view should allow for
|
||||
/// dynamic rendering of the background
|
||||
@available(*, deprecated, message: "liveBlur has been deprecated and will be removed with future versions of PopupDialog. Please use isLiveBlurEnabled instead.")
|
||||
@objc public dynamic var liveBlur: Bool {
|
||||
get { return liveBlurEnabled }
|
||||
set { liveBlurEnabled = newValue }
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// PopupDialogPresentationController.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
final internal class PresentationController: UIPresentationController {
|
||||
|
||||
private lazy var overlay: PopupDialogOverlayView = {
|
||||
return PopupDialogOverlayView(frame: .zero)
|
||||
}()
|
||||
|
||||
override func presentationTransitionWillBegin() {
|
||||
|
||||
guard let containerView = containerView else { return }
|
||||
|
||||
overlay.frame = containerView.bounds
|
||||
containerView.insertSubview(overlay, at: 0)
|
||||
|
||||
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { [weak self] _ in
|
||||
self?.overlay.alpha = 1.0
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
override func dismissalTransitionWillBegin() {
|
||||
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { [weak self] _ in
|
||||
self?.overlay.alpha = 0.0
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
override func containerViewWillLayoutSubviews() {
|
||||
|
||||
guard let presentedView = presentedView else { return }
|
||||
|
||||
presentedView.frame = frameOfPresentedViewInContainerView
|
||||
overlay.blurView.refresh()
|
||||
}
|
||||
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// PopupDialogPresentationManager.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
final internal class PresentationManager: NSObject, UIViewControllerTransitioningDelegate {
|
||||
|
||||
var transitionStyle: PopupDialogTransitionStyle
|
||||
var interactor: InteractiveTransition
|
||||
|
||||
init(transitionStyle: PopupDialogTransitionStyle, interactor: InteractiveTransition) {
|
||||
self.transitionStyle = transitionStyle
|
||||
self.interactor = interactor
|
||||
super.init()
|
||||
}
|
||||
|
||||
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
|
||||
let presentationController = PresentationController(presentedViewController: presented, presenting: source)
|
||||
return presentationController
|
||||
}
|
||||
|
||||
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
|
||||
var transition: TransitionAnimator
|
||||
switch transitionStyle {
|
||||
case .bounceUp:
|
||||
transition = BounceUpTransition(direction: .in)
|
||||
case .bounceDown:
|
||||
transition = BounceDownTransition(direction: .in)
|
||||
case .zoomIn:
|
||||
transition = ZoomTransition(direction: .in)
|
||||
case .fadeIn:
|
||||
transition = FadeTransition(direction: .in)
|
||||
}
|
||||
|
||||
return transition
|
||||
}
|
||||
|
||||
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
|
||||
if interactor.hasStarted || interactor.shouldFinish {
|
||||
return DismissInteractiveTransition()
|
||||
}
|
||||
|
||||
var transition: TransitionAnimator
|
||||
switch transitionStyle {
|
||||
case .bounceUp:
|
||||
transition = BounceUpTransition(direction: .out)
|
||||
case .bounceDown:
|
||||
transition = BounceDownTransition(direction: .out)
|
||||
case .zoomIn:
|
||||
transition = ZoomTransition(direction: .out)
|
||||
case .fadeIn:
|
||||
transition = FadeTransition(direction: .out)
|
||||
}
|
||||
|
||||
return transition
|
||||
}
|
||||
|
||||
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
|
||||
return interactor.hasStarted ? interactor : nil
|
||||
}
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
//
|
||||
// PopupDialogTransitionAnimations.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/*!
|
||||
Presentation transition styles for the popup dialog
|
||||
|
||||
- BounceUp: Dialog bounces in from bottom and is dismissed to bottom
|
||||
- BounceDown: Dialog bounces in from top and is dismissed to top
|
||||
- ZoomIn: Dialog zooms in and is dismissed by zooming out
|
||||
- FadeIn: Dialog fades in and is dismissed by fading out
|
||||
*/
|
||||
@objc public enum PopupDialogTransitionStyle: Int {
|
||||
case bounceUp
|
||||
case bounceDown
|
||||
case zoomIn
|
||||
case fadeIn
|
||||
}
|
||||
|
||||
/// Dialog bounces in from bottom and is dismissed to bottom
|
||||
final internal class BounceUpTransition: TransitionAnimator {
|
||||
|
||||
init(direction: AnimationDirection) {
|
||||
super.init(inDuration: 0.22, outDuration: 0.2, direction: direction)
|
||||
}
|
||||
|
||||
override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
super.animateTransition(using: transitionContext)
|
||||
|
||||
switch direction {
|
||||
case .in:
|
||||
to.view.bounds.origin = CGPoint(x: 0, y: -from.view.bounds.size.height)
|
||||
UIView.animate(withDuration: 0.6, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0, options: [.curveEaseOut], animations: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.to.view.bounds = self.from.view.bounds
|
||||
}, completion: { _ in
|
||||
transitionContext.completeTransition(true)
|
||||
})
|
||||
case .out:
|
||||
UIView.animate(withDuration: outDuration, delay: 0.0, options: [.curveEaseIn], animations: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.from.view.bounds.origin = CGPoint(x: 0, y: -self.from.view.bounds.size.height)
|
||||
self.from.view.alpha = 0.0
|
||||
}, completion: { _ in
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Dialog bounces in from top and is dismissed to top
|
||||
final internal class BounceDownTransition: TransitionAnimator {
|
||||
|
||||
init(direction: AnimationDirection) {
|
||||
super.init(inDuration: 0.22, outDuration: 0.2, direction: direction)
|
||||
}
|
||||
|
||||
override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
super.animateTransition(using: transitionContext)
|
||||
|
||||
switch direction {
|
||||
case .in:
|
||||
to.view.bounds.origin = CGPoint(x: 0, y: from.view.bounds.size.height)
|
||||
UIView.animate(withDuration: 0.6, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0, options: [.curveEaseOut], animations: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.to.view.bounds = self.from.view.bounds
|
||||
}, completion: { _ in
|
||||
transitionContext.completeTransition(true)
|
||||
})
|
||||
case .out:
|
||||
UIView.animate(withDuration: outDuration, delay: 0.0, options: [.curveEaseIn], animations: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.from.view.bounds.origin = CGPoint(x: 0, y: self.from.view.bounds.size.height)
|
||||
self.from.view.alpha = 0.0
|
||||
}, completion: { _ in
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dialog zooms in and is dismissed by zooming out
|
||||
final internal class ZoomTransition: TransitionAnimator {
|
||||
|
||||
init(direction: AnimationDirection) {
|
||||
super.init(inDuration: 0.22, outDuration: 0.2, direction: direction)
|
||||
}
|
||||
|
||||
override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
super.animateTransition(using: transitionContext)
|
||||
|
||||
switch direction {
|
||||
case .in:
|
||||
to.view.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
|
||||
UIView.animate(withDuration: 0.6, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0, options: [.curveEaseOut], animations: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.to.view.transform = CGAffineTransform(scaleX: 1, y: 1)
|
||||
}, completion: { _ in
|
||||
transitionContext.completeTransition(true)
|
||||
})
|
||||
case .out:
|
||||
UIView.animate(withDuration: outDuration, delay: 0.0, options: [.curveEaseIn], animations: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.from.view.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
|
||||
self.from.view.alpha = 0.0
|
||||
}, completion: { _ in
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dialog fades in and is dismissed by fading out
|
||||
final internal class FadeTransition: TransitionAnimator {
|
||||
|
||||
init(direction: AnimationDirection) {
|
||||
super.init(inDuration: 0.22, outDuration: 0.2, direction: direction)
|
||||
}
|
||||
|
||||
override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
super.animateTransition(using: transitionContext)
|
||||
|
||||
switch direction {
|
||||
case .in:
|
||||
to.view.alpha = 0
|
||||
UIView.animate(withDuration: 0.6, delay: 0.0, options: [.curveEaseOut],
|
||||
animations: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.to.view.alpha = 1
|
||||
}, completion: { _ in
|
||||
transitionContext.completeTransition(true)
|
||||
})
|
||||
case .out:
|
||||
UIView.animate(withDuration: outDuration, delay: 0.0, options: [.curveEaseIn], animations: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.from.view.alpha = 0.0
|
||||
}, completion: { _ in
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for the always drop out animation with pan gesture dismissal
|
||||
final internal class DismissInteractiveTransition: TransitionAnimator {
|
||||
|
||||
init() {
|
||||
super.init(inDuration: 0.22, outDuration: 0.32, direction: .out)
|
||||
}
|
||||
|
||||
override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
super.animateTransition(using: transitionContext)
|
||||
UIView.animate(withDuration: outDuration, delay: 0.0, options: [.beginFromCurrentState], animations: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.from.view.bounds.origin = CGPoint(x: 0, y: -self.from.view.bounds.size.height)
|
||||
self.from.view.alpha = 0.0
|
||||
}, completion: { _ in
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
})
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// PopupDialogTransitionAnimator.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// Base class for custom transition animations
|
||||
internal class TransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
var to: UIViewController!
|
||||
var from: UIViewController!
|
||||
let inDuration: TimeInterval
|
||||
let outDuration: TimeInterval
|
||||
let direction: AnimationDirection
|
||||
|
||||
init(inDuration: TimeInterval, outDuration: TimeInterval, direction: AnimationDirection) {
|
||||
self.inDuration = inDuration
|
||||
self.outDuration = outDuration
|
||||
self.direction = direction
|
||||
super.init()
|
||||
}
|
||||
|
||||
internal func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
return direction == .in ? inDuration : outDuration
|
||||
}
|
||||
|
||||
internal func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
switch direction {
|
||||
case .in:
|
||||
guard let to = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to),
|
||||
let from = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) else { return }
|
||||
|
||||
self.to = to
|
||||
self.from = from
|
||||
|
||||
let container = transitionContext.containerView
|
||||
container.addSubview(to.view)
|
||||
case .out:
|
||||
guard let to = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to),
|
||||
let from = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) else { return }
|
||||
|
||||
self.to = to
|
||||
self.from = from
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// UIImageView+Calculations.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
internal extension UIImageView {
|
||||
|
||||
/*!
|
||||
Calculates the height of the the UIImageView has to
|
||||
have so the image is displayed correctly
|
||||
- returns: Height to set on the imageView
|
||||
*/
|
||||
func pv_heightForImageView() -> CGFloat {
|
||||
guard let image = image, image.size.height > 0 else {
|
||||
return 0.0
|
||||
}
|
||||
let width = bounds.size.width
|
||||
let ratio = image.size.height / image.size.width
|
||||
return width * ratio
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// UIView+Animations.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/*!
|
||||
The intended direction of the animation
|
||||
- in: Animate in
|
||||
- out: Animate out
|
||||
*/
|
||||
internal enum AnimationDirection {
|
||||
case `in` // swiftlint:disable:this identifier_name
|
||||
case out
|
||||
}
|
||||
|
||||
internal extension UIView {
|
||||
|
||||
/// The key for the fade animation
|
||||
var fadeKey: String { return "FadeAnimation" }
|
||||
var shakeKey: String { return "ShakeAnimation" }
|
||||
|
||||
func pv_fade(_ direction: AnimationDirection, _ value: Float, duration: CFTimeInterval = 0.08) {
|
||||
layer.removeAnimation(forKey: fadeKey)
|
||||
let animation = CABasicAnimation(keyPath: "opacity")
|
||||
animation.duration = duration
|
||||
animation.fromValue = layer.presentation()?.opacity
|
||||
layer.opacity = value
|
||||
animation.fillMode = CAMediaTimingFillMode.forwards
|
||||
layer.add(animation, forKey: fadeKey)
|
||||
}
|
||||
|
||||
func pv_layoutIfNeededAnimated(duration: CFTimeInterval = 0.08) {
|
||||
UIView.animate(withDuration: duration, delay: 0, options: UIView.AnimationOptions(), animations: {
|
||||
self.layoutIfNeeded()
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
// As found at https://gist.github.com/mourad-brahim/cf0bfe9bec5f33a6ea66#file-uiview-animations-swift-L9
|
||||
// Slightly modified
|
||||
func pv_shake() {
|
||||
layer.removeAnimation(forKey: shakeKey)
|
||||
let vals: [Double] = [-2, 2, -2, 2, 0]
|
||||
|
||||
let translation = CAKeyframeAnimation(keyPath: "transform.translation.x")
|
||||
translation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||
translation.values = vals
|
||||
|
||||
let rotation = CAKeyframeAnimation(keyPath: "transform.rotation.z")
|
||||
rotation.values = vals.map { (degrees: Double) in
|
||||
let radians: Double = (Double.pi * degrees) / 180.0
|
||||
return radians
|
||||
}
|
||||
|
||||
let shakeGroup: CAAnimationGroup = CAAnimationGroup()
|
||||
shakeGroup.animations = [translation, rotation]
|
||||
shakeGroup.duration = 0.3
|
||||
self.layer.add(shakeGroup, forKey: shakeKey)
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// UIViewController+Visibility.swift
|
||||
//
|
||||
// Copyright (c) 2016 Orderella Ltd. (http://orderella.co.uk)
|
||||
// Author - Martin Wildfeuer (http://www.mwfire.de)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
// http://stackoverflow.com/questions/2777438/how-to-tell-if-uiviewcontrollers-view-is-visible
|
||||
internal extension UIViewController {
|
||||
|
||||
var isTopAndVisible: Bool {
|
||||
return isVisible && isTopViewController
|
||||
}
|
||||
|
||||
var isVisible: Bool {
|
||||
if isViewLoaded {
|
||||
return view.window != nil
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var isTopViewController: Bool {
|
||||
if self.navigationController != nil {
|
||||
return self.navigationController?.visibleViewController === self
|
||||
} else if self.tabBarController != nil {
|
||||
return self.tabBarController?.selectedViewController == self && self.presentedViewController == nil
|
||||
} else {
|
||||
return self.presentedViewController == nil && self.isVisible
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// ScrollRects.swift
|
||||
// TableViewDragger
|
||||
//
|
||||
// Created by Kyohei Ito on 2017/12/08.
|
||||
// Copyright © 2017年 kyohei_ito. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreImage
|
||||
|
||||
struct ScrollRects {
|
||||
private let maxDistance: CGFloat = 10
|
||||
private let size: CGSize
|
||||
private let scrollRange: CGFloat
|
||||
|
||||
let topRect: CGRect
|
||||
let bottomRect: CGRect
|
||||
|
||||
init(size: CGSize) {
|
||||
self.size = size
|
||||
scrollRange = size.height / 2.5
|
||||
|
||||
let scrollSize = CGSize(width: size.width, height: scrollRange)
|
||||
topRect = CGRect(origin: .zero, size: scrollSize)
|
||||
bottomRect = CGRect(origin: CGPoint(x: 0, y: size.height - scrollRange), size: scrollSize)
|
||||
}
|
||||
|
||||
func distance(at point: CGPoint) -> CGFloat {
|
||||
let ratio: CGFloat
|
||||
if topRect.contains(point) {
|
||||
ratio = -(scrollRange - point.y)
|
||||
} else if bottomRect.contains(point) {
|
||||
ratio = point.y - (size.height - scrollRange)
|
||||
} else {
|
||||
ratio = 0
|
||||
}
|
||||
|
||||
return max(min(ratio / 30, maxDistance), -maxDistance)
|
||||
}
|
||||
}
|
||||
+371
@@ -0,0 +1,371 @@
|
||||
//
|
||||
// TableViewDragger.swift
|
||||
// TableViewDragger
|
||||
//
|
||||
// Created by Kyohei Ito on 2015/09/24.
|
||||
// Copyright © 2015年 kyohei_ito. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objc public protocol TableViewDraggerDelegate: class {
|
||||
/// If allow movement of cell, please return `true`. require a call to `moveRowAt:toIndexPath:` of UITableView and rearranged of data.
|
||||
func dragger(_ dragger: TableViewDragger, moveDraggingAt indexPath: IndexPath, newIndexPath: IndexPath) -> Bool
|
||||
|
||||
/// If allow dragging of cell, prease return `true`.
|
||||
@objc optional func dragger(_ dragger: TableViewDragger, shouldDragAt indexPath: IndexPath) -> Bool
|
||||
@objc optional func dragger(_ dragger: TableViewDragger, willBeginDraggingAt indexPath: IndexPath)
|
||||
@objc optional func dragger(_ dragger: TableViewDragger, didBeginDraggingAt indexPath: IndexPath)
|
||||
@objc optional func dragger(_ dragger: TableViewDragger, willEndDraggingAt indexPath: IndexPath)
|
||||
@objc optional func dragger(_ dragger: TableViewDragger, didEndDraggingAt indexPath: IndexPath)
|
||||
}
|
||||
|
||||
@objc public protocol TableViewDraggerDataSource: class {
|
||||
/// Return any cell if want to change the cell in drag.
|
||||
@objc optional func dragger(_ dragger: TableViewDragger, cellForRowAt indexPath: IndexPath) -> UIView?
|
||||
/// Return the indexPath if want to change the indexPath to start drag.
|
||||
@objc optional func dragger(_ dragger: TableViewDragger, indexPathForDragAt indexPath: IndexPath) -> IndexPath
|
||||
}
|
||||
|
||||
open class TableViewDragger: NSObject {
|
||||
let longPressGesture = UILongPressGestureRecognizer()
|
||||
let panGesture = UIPanGestureRecognizer()
|
||||
var draggingCell: TableViewDraggerCell?
|
||||
var displayLink: CADisplayLink?
|
||||
var targetClipsToBounds = true
|
||||
weak var targetTableView: UITableView?
|
||||
private var draggingDirection: UIScrollView.DraggingDirection?
|
||||
|
||||
/// It will be `true` if want to hide the original cell.
|
||||
open var isHiddenOriginCell: Bool = true
|
||||
/// Zoom scale of cell in drag.
|
||||
open var zoomScaleForCell: CGFloat = 1
|
||||
/// Alpha of cell in drag.
|
||||
open var alphaForCell: CGFloat = 1
|
||||
/// Opacity of cell shadow in drag.
|
||||
open var opacityForShadowOfCell: Float = 0.4
|
||||
/// Velocity of auto scroll in drag.
|
||||
open var scrollVelocity: CGFloat = 1
|
||||
open weak var delegate: TableViewDraggerDelegate?
|
||||
open weak var dataSource: TableViewDraggerDataSource?
|
||||
//
|
||||
open var availableHorizontalScroll : Bool = true
|
||||
open var tableView: UITableView? {
|
||||
return targetTableView
|
||||
}
|
||||
|
||||
/// `UITableView` want to drag.
|
||||
public init(tableView: UITableView, _ minimumPressDuration: CFTimeInterval = 0.5) {
|
||||
super.init()
|
||||
|
||||
self.targetTableView = tableView
|
||||
tableView.addGestureRecognizer(longPressGesture)
|
||||
tableView.addGestureRecognizer(panGesture)
|
||||
|
||||
longPressGesture.addTarget(self, action: #selector(TableViewDragger.longPressGestureAction(_:)))
|
||||
longPressGesture.delegate = self
|
||||
longPressGesture.allowableMovement = 5.0
|
||||
longPressGesture.minimumPressDuration = minimumPressDuration
|
||||
|
||||
panGesture.addTarget(self, action: #selector(TableViewDragger.panGestureAction(_:)))
|
||||
panGesture.delegate = self
|
||||
panGesture.maximumNumberOfTouches = 1
|
||||
}
|
||||
|
||||
deinit {
|
||||
targetTableView?.removeGestureRecognizer(longPressGesture)
|
||||
targetTableView?.removeGestureRecognizer(panGesture)
|
||||
}
|
||||
|
||||
func targetIndexPath(_ tableView: UITableView, draggingCell: TableViewDraggerCell) -> IndexPath {
|
||||
let location = draggingCell.location
|
||||
let offsetY = (draggingCell.viewHeight / 2) + 2
|
||||
let offsetX = tableView.center.x
|
||||
let topPoint = CGPoint(x: offsetX, y: location.y - offsetY)
|
||||
let bottomPoint = CGPoint(x: offsetX, y: location.y + offsetY)
|
||||
let point = draggingDirection == .up ? topPoint : bottomPoint
|
||||
|
||||
if let targetIndexPath = tableView.indexPathForRow(at: point) {
|
||||
if tableView.cellForRow(at: targetIndexPath) == nil {
|
||||
return draggingCell.dropIndexPath
|
||||
}
|
||||
|
||||
let targetRect = tableView.rectForRow(at: targetIndexPath)
|
||||
let targetCenterY = targetRect.origin.y + (targetRect.height / 2)
|
||||
|
||||
guard let direction = draggingDirection else {
|
||||
return draggingCell.dropIndexPath
|
||||
}
|
||||
|
||||
switch direction {
|
||||
case .up:
|
||||
if (targetCenterY > point.y && draggingCell.dropIndexPath > targetIndexPath) {
|
||||
return targetIndexPath
|
||||
}
|
||||
case .down:
|
||||
if (targetCenterY < point.y && draggingCell.dropIndexPath < targetIndexPath) {
|
||||
return targetIndexPath
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let section = (0..<tableView.numberOfSections).filter { section -> Bool in
|
||||
tableView.rect(forSection: section).contains(point)
|
||||
}.first
|
||||
|
||||
if let section = section, tableView.numberOfRows(inSection: section) == 0 {
|
||||
return IndexPath(row: 0, section: section)
|
||||
}
|
||||
}
|
||||
|
||||
return draggingCell.dropIndexPath
|
||||
}
|
||||
|
||||
func dragCell(_ tableView: UITableView, draggingCell: TableViewDraggerCell) {
|
||||
let indexPath = targetIndexPath(tableView, draggingCell: draggingCell)
|
||||
if draggingCell.dropIndexPath.compare(indexPath) == .orderedSame {
|
||||
return
|
||||
}
|
||||
|
||||
if let cell = tableView.cellForRow(at: draggingCell.dropIndexPath) {
|
||||
cell.isHidden = isHiddenOriginCell
|
||||
}
|
||||
if delegate?.dragger(self, moveDraggingAt: draggingCell.dropIndexPath, newIndexPath: indexPath) == true {
|
||||
draggingCell.dropIndexPath = indexPath
|
||||
}
|
||||
}
|
||||
|
||||
func copiedCell(at indexPath: IndexPath) -> UIView? {
|
||||
if let view = dataSource?.dragger?(self, cellForRowAt: indexPath) {
|
||||
return view
|
||||
}
|
||||
|
||||
if let cell = targetTableView?.cellForRow(at: indexPath) {
|
||||
if let view = cell.snapshotView(afterScreenUpdates: false) {
|
||||
return view
|
||||
} else if let view = cell.captured() {
|
||||
view.frame = cell.bounds
|
||||
return view
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func draggedCell(_ tableView: UITableView, indexPath: IndexPath) -> TableViewDraggerCell? {
|
||||
guard let copiedCell = copiedCell(at: indexPath) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let cellRect = tableView.rectForRow(at: indexPath)
|
||||
copiedCell.frame.size = cellRect.size
|
||||
|
||||
if let height = tableView.delegate?.tableView?(tableView, heightForRowAt: indexPath) {
|
||||
copiedCell.frame.size.height = height
|
||||
}
|
||||
|
||||
let cell = TableViewDraggerCell(cell: copiedCell)
|
||||
cell.dragScale = zoomScaleForCell
|
||||
cell.dragAlpha = alphaForCell
|
||||
cell.dragShadowOpacity = opacityForShadowOfCell
|
||||
cell.dropIndexPath = indexPath
|
||||
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Dragging Cell
|
||||
extension TableViewDragger {
|
||||
private func draggingDidBegin(_ gesture: UIGestureRecognizer, indexPath: IndexPath) {
|
||||
displayLink?.invalidate()
|
||||
displayLink = UIScreen.main.displayLink(withTarget: self, selector: #selector(TableViewDragger.displayDidRefresh(_:)))
|
||||
displayLink?.add(to: .main, forMode: .default)
|
||||
displayLink?.isPaused = true
|
||||
|
||||
let dragIndexPath = dataSource?.dragger?(self, indexPathForDragAt: indexPath) ?? indexPath
|
||||
delegate?.dragger?(self, willBeginDraggingAt: dragIndexPath)
|
||||
|
||||
if let tableView = targetTableView {
|
||||
let actualCell = tableView.cellForRow(at: dragIndexPath)
|
||||
|
||||
if let draggedCell = draggedCell(tableView, indexPath: dragIndexPath) {
|
||||
let point = gesture.location(in: actualCell)
|
||||
|
||||
if availableHorizontalScroll == true{
|
||||
|
||||
draggedCell.offset = point
|
||||
draggedCell.transformToPoint(point)
|
||||
draggedCell.location = gesture.location(in: tableView)
|
||||
|
||||
} else {
|
||||
|
||||
draggedCell.offset = CGPoint(x: (draggedCell.frame.size.width)/2, y: point.y)
|
||||
draggedCell.transformToPoint(CGPoint(x: (draggedCell.frame.size.width)/2, y: point.y))
|
||||
draggedCell.location = CGPoint(x: (draggedCell.frame.size.width)/2, y: gesture.location(in: tableView).y)
|
||||
|
||||
}
|
||||
|
||||
tableView.addSubview(draggedCell)
|
||||
draggingCell = draggedCell
|
||||
}
|
||||
|
||||
actualCell?.isHidden = isHiddenOriginCell
|
||||
targetClipsToBounds = tableView.clipsToBounds
|
||||
tableView.clipsToBounds = false
|
||||
}
|
||||
|
||||
delegate?.dragger?(self, didBeginDraggingAt: indexPath)
|
||||
}
|
||||
|
||||
private func draggingDidChange(_ gesture: UIGestureRecognizer, direction: UIScrollView.DraggingDirection?) {
|
||||
guard let tableView = targetTableView, let draggingCell = draggingCell else {
|
||||
return
|
||||
}
|
||||
|
||||
if availableHorizontalScroll == true{
|
||||
|
||||
draggingCell.location = gesture.location(in: tableView)
|
||||
|
||||
} else {
|
||||
|
||||
draggingCell.location = CGPoint(x: (draggingCell.frame.size.width)/2, y: gesture.location(in: tableView).y)
|
||||
}
|
||||
|
||||
|
||||
if let adjustedDirection = tableView.draggingDirection(at: draggingCell.adjustedCenter(on: tableView)) {
|
||||
displayLink?.isPaused = false
|
||||
draggingDirection = adjustedDirection
|
||||
} else {
|
||||
draggingDirection = direction
|
||||
}
|
||||
|
||||
|
||||
dragCell(tableView, draggingCell: draggingCell)
|
||||
}
|
||||
|
||||
private func draggingDidEnd(_ gesture: UIGestureRecognizer) {
|
||||
displayLink?.invalidate()
|
||||
displayLink = nil
|
||||
|
||||
guard let tableView = targetTableView, let draggingCell = draggingCell else {
|
||||
return
|
||||
}
|
||||
|
||||
delegate?.dragger?(self, willEndDraggingAt: draggingCell.dropIndexPath)
|
||||
|
||||
let targetRect = tableView.rectForRow(at: draggingCell.dropIndexPath)
|
||||
let center = CGPoint(x: targetRect.width / 2, y: targetRect.origin.y + (targetRect.height / 2))
|
||||
|
||||
draggingCell.drop(center) {
|
||||
self.delegate?.dragger?(self, didEndDraggingAt: draggingCell.dropIndexPath)
|
||||
|
||||
if let cell = tableView.cellForRow(at: draggingCell.dropIndexPath) {
|
||||
cell.isHidden = false
|
||||
}
|
||||
|
||||
tableView.clipsToBounds = self.targetClipsToBounds
|
||||
|
||||
self.draggingCell = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Action Methods
|
||||
private extension TableViewDragger {
|
||||
@objc func displayDidRefresh(_ displayLink: CADisplayLink) {
|
||||
guard let tableView = targetTableView, let draggingCell = draggingCell else {
|
||||
return
|
||||
}
|
||||
|
||||
let center = draggingCell.adjustedCenter(on: tableView)
|
||||
|
||||
if let direction = tableView.draggingDirection(at: center) {
|
||||
draggingDirection = direction
|
||||
} else {
|
||||
displayLink.isPaused = true
|
||||
}
|
||||
|
||||
tableView.contentOffset = tableView.preferredContentOffset(at: center, velocity: scrollVelocity)
|
||||
|
||||
dragCell(tableView, draggingCell: draggingCell)
|
||||
|
||||
if availableHorizontalScroll == true{
|
||||
|
||||
draggingCell.location = panGesture.location(in: tableView)
|
||||
} else {
|
||||
|
||||
draggingCell.location = CGPoint(x: draggingCell.frame.size.width/2, y: panGesture.location(in: tableView).y)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc func longPressGestureAction(_ gesture: UILongPressGestureRecognizer) {
|
||||
switch gesture.state {
|
||||
case .began:
|
||||
targetTableView?.isScrollEnabled = false
|
||||
|
||||
let point = gesture.location(in: targetTableView)
|
||||
if let path = targetTableView?.indexPathForRow(at: point) {
|
||||
draggingDidBegin(gesture, indexPath: path)
|
||||
}
|
||||
case .ended, .cancelled:
|
||||
draggingDidEnd(gesture)
|
||||
|
||||
targetTableView?.isScrollEnabled = true
|
||||
|
||||
case .changed, .failed, .possible:
|
||||
break
|
||||
@unknown default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@objc func panGestureAction(_ gesture: UIPanGestureRecognizer) {
|
||||
guard targetTableView?.isScrollEnabled == false && gesture.state == .changed else {
|
||||
return
|
||||
}
|
||||
|
||||
let offsetY = gesture.translation(in: targetTableView).y
|
||||
if offsetY < 0 {
|
||||
draggingDidChange(gesture, direction: .up)
|
||||
} else if offsetY > 0 {
|
||||
draggingDidChange(gesture, direction: .down)
|
||||
} else {
|
||||
draggingDidChange(gesture, direction: nil)
|
||||
}
|
||||
|
||||
gesture.setTranslation(.zero, in: targetTableView)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIGestureRecognizerDelegate Methods
|
||||
extension TableViewDragger: UIGestureRecognizerDelegate {
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
||||
if gestureRecognizer == longPressGesture {
|
||||
let point = touch.location(in: targetTableView)
|
||||
|
||||
if let indexPath = targetTableView?.indexPathForRow(at: point) {
|
||||
if let ret = delegate?.dragger?(self, shouldDragAt: indexPath) {
|
||||
return ret
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return gestureRecognizer == panGesture || otherGestureRecognizer == panGesture || gestureRecognizer == longPressGesture || otherGestureRecognizer == longPressGesture
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
fileprivate func captured() -> UIView? {
|
||||
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [UIView.self], from: data) as? UIView
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// TableViewDraggerCell.swift
|
||||
// TableViewDragger
|
||||
//
|
||||
// Created by Kyohei Ito on 2015/09/24.
|
||||
// Copyright © 2015年 kyohei_ito. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class TableViewDraggerCell: UIScrollView {
|
||||
private let zoomingView: UIView!
|
||||
|
||||
var dragAlpha: CGFloat = 1
|
||||
var dragScale: CGFloat = 1
|
||||
var dragShadowOpacity: Float = 0.4
|
||||
|
||||
var dropIndexPath: IndexPath = IndexPath(index: 0)
|
||||
var offset: CGPoint = CGPoint.zero {
|
||||
didSet {
|
||||
offset.x -= (bounds.width / 2)
|
||||
offset.y -= (bounds.height / 2)
|
||||
center = adjustCenter(location)
|
||||
}
|
||||
}
|
||||
var location: CGPoint = CGPoint.zero {
|
||||
didSet {
|
||||
center = adjustCenter(location)
|
||||
}
|
||||
}
|
||||
var viewHeight: CGFloat {
|
||||
return zoomingView.bounds.height * zoomScale
|
||||
}
|
||||
|
||||
private func adjustCenter(_ center: CGPoint) -> CGPoint {
|
||||
var center = center
|
||||
center.x -= offset.x
|
||||
center.y -= offset.y
|
||||
return center
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
zoomingView = UIView(frame: .zero)
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
init(cell: UIView) {
|
||||
zoomingView = UIView(frame: cell.bounds)
|
||||
zoomingView.addSubview(cell)
|
||||
|
||||
super.init(frame: cell.bounds)
|
||||
|
||||
delegate = self
|
||||
clipsToBounds = false
|
||||
|
||||
layer.shadowColor = UIColor.black.cgColor
|
||||
layer.shadowOpacity = 0
|
||||
layer.shadowRadius = 5
|
||||
layer.shadowOffset = .zero
|
||||
|
||||
addSubview(zoomingView)
|
||||
}
|
||||
|
||||
func transformToPoint(_ point: CGPoint) {
|
||||
if dragScale > 1 {
|
||||
maximumZoomScale = dragScale
|
||||
} else {
|
||||
minimumZoomScale = dragScale
|
||||
}
|
||||
|
||||
var center = zoomingView.center
|
||||
center.x -= (center.x * dragScale) - point.x
|
||||
center.y -= (center.y * dragScale) - point.y
|
||||
|
||||
UIView.animate(withDuration: 0.25, delay: 0.1, options: .curveEaseInOut, animations: {
|
||||
self.zoomingView.center = center
|
||||
self.zoomScale = self.dragScale
|
||||
self.alpha = self.dragAlpha
|
||||
}, completion: nil)
|
||||
|
||||
CATransaction.begin()
|
||||
let anim = CABasicAnimation(keyPath: "shadowOpacity")
|
||||
anim.fromValue = 0
|
||||
anim.toValue = dragShadowOpacity
|
||||
anim.duration = 0.1
|
||||
anim.isRemovedOnCompletion = false
|
||||
anim.fillMode = .forwards
|
||||
layer.add(anim, forKey: "cellDragAnimation")
|
||||
CATransaction.commit()
|
||||
}
|
||||
|
||||
func adjustedCenter(on scrollView: UIScrollView) -> CGPoint {
|
||||
var center = location
|
||||
center.y -= scrollView.contentOffset.y
|
||||
center.x = scrollView.center.x
|
||||
return center
|
||||
}
|
||||
|
||||
func drop(_ center: CGPoint, completion: (() -> Void)? = nil) {
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.zoomingView.adjustCenterAtRect(self.zoomingView.frame)
|
||||
self.center = center
|
||||
self.zoomScale = 1.0
|
||||
self.alpha = 1.0
|
||||
}
|
||||
|
||||
CATransaction.begin()
|
||||
let anim = CABasicAnimation(keyPath: "shadowOpacity")
|
||||
anim.fromValue = dragShadowOpacity
|
||||
anim.toValue = 0
|
||||
anim.duration = 0.15
|
||||
anim.beginTime = CACurrentMediaTime() + 0.15
|
||||
anim.isRemovedOnCompletion = false
|
||||
anim.fillMode = .forwards
|
||||
CATransaction.setCompletionBlock {
|
||||
self.removeFromSuperview()
|
||||
|
||||
completion?()
|
||||
}
|
||||
layer.add(anim, forKey: "cellDropAnimation")
|
||||
CATransaction.commit()
|
||||
}
|
||||
}
|
||||
|
||||
extension TableViewDraggerCell: UIScrollViewDelegate {
|
||||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||
return zoomingView
|
||||
}
|
||||
}
|
||||
|
||||
private extension UIView {
|
||||
func adjustCenterAtRect(_ rect: CGRect) {
|
||||
let center = CGPoint(x: rect.size.width / 2, y: rect.size.height / 2)
|
||||
self.center = center
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// UIScrollViewExtension.swift
|
||||
// Pods
|
||||
//
|
||||
// Created by Kyohei Ito on 2015/09/29.
|
||||
//
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIScrollView {
|
||||
enum DraggingDirection {
|
||||
case up
|
||||
case down
|
||||
}
|
||||
|
||||
func preferredContentOffset(at point: CGPoint, velocity: CGFloat) -> CGPoint {
|
||||
let distance = ScrollRects(size: bounds.size).distance(at: point) / velocity
|
||||
var offset = contentOffset
|
||||
offset.y += distance
|
||||
|
||||
let topOffset = -contentInset.top
|
||||
let bottomOffset = contentInset.bottom
|
||||
let height = floor(contentSize.height) - bounds.size.height
|
||||
|
||||
if offset.y > height + bottomOffset {
|
||||
offset.y = height + bottomOffset
|
||||
} else if offset.y < topOffset {
|
||||
offset.y = topOffset
|
||||
}
|
||||
|
||||
return offset
|
||||
}
|
||||
|
||||
func draggingDirection(at point: @autoclosure () -> CGPoint) -> DraggingDirection? {
|
||||
let contentHeight = floor(contentSize.height)
|
||||
if bounds.size.height >= contentHeight {
|
||||
return nil
|
||||
}
|
||||
|
||||
let rects = ScrollRects(size: bounds.size)
|
||||
let point = point()
|
||||
|
||||
if rects.topRect.contains(point) {
|
||||
let topOffset = -contentInset.top
|
||||
if contentOffset.y > topOffset {
|
||||
return .up
|
||||
}
|
||||
} else if rects.bottomRect.contains(point) {
|
||||
let bottomOffset = contentHeight + contentInset.bottom - bounds.size.height
|
||||
if contentOffset.y < bottomOffset {
|
||||
return .down
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user