Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 363816d6ad | |||
| 9d7f65772b | |||
| cef6a6188a | |||
| 1515f6761b | |||
| 8fee841003 | |||
| ee85248b23 | |||
| c135346125 | |||
| bd7ee080bd | |||
| 3929205637 | |||
| 13f860cc37 | |||
| 3c853abfe0 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
+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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+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
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// ViewController+MetalVideoProcessPlayerDelegate.swift
|
||||
// SimpleVideoEditor
|
||||
//
|
||||
// Created by RenZhu Macro on 2020/7/9.
|
||||
// Copyright © 2020 RenZhu Macro. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MetalVideoProcess
|
||||
import AVFoundation
|
||||
import AVKit
|
||||
|
||||
extension ViewController: 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?) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
if error != nil {
|
||||
MBProgressHUD.hide(for: self.view, animated: true)
|
||||
guard let source = self.movieWriter?.sources.sources[0] else {
|
||||
return
|
||||
}
|
||||
source.removeAllTargets()
|
||||
source --> self.renderView
|
||||
self.view.isUserInteractionEnabled = true
|
||||
let alertVC = UIAlertController(title: "", message: "导出失败, 请重新尝试", preferredStyle: .alert)
|
||||
|
||||
let alertBtn = UIAlertAction(title: "确认", style: .cancel) { (_) in
|
||||
alertVC.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
alertVC.addAction(alertBtn)
|
||||
self.present(alertVC, animated: true) {
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
if error != nil {
|
||||
self.view.isUserInteractionEnabled = true
|
||||
return
|
||||
}
|
||||
//这里准备调用writer结束
|
||||
self.movieWriter?.finishRecording({
|
||||
//这里去预览界面播放编辑后的视频
|
||||
DispatchQueue.main.async {
|
||||
MBProgressHUD.hide(for: self.view, animated: true)
|
||||
guard let source = self.movieWriter?.sources.sources[0] else {
|
||||
return
|
||||
}
|
||||
source.removeAllTargets()
|
||||
source --> self.renderView
|
||||
|
||||
let playerVC = AVPlayerViewController()
|
||||
playerVC.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Info", style: .done, target: self, action: #selector(self.showInfo))
|
||||
playerVC.player = AVPlayer.init(url: self.movieWriter!.fileURL)
|
||||
self.present(playerVC, animated: true, completion: nil)
|
||||
self.view.isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func exportProgressChanged(_ progress: Float) {
|
||||
DispatchQueue.main.async {
|
||||
self.progressHUD?.progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
@objc func showInfo() {
|
||||
let data = NSData(contentsOf: self.movieWriter!.fileURL)
|
||||
guard let size = data?.length else {
|
||||
return
|
||||
}
|
||||
|
||||
let mSize = Double(size) / (1024 * 1024)
|
||||
let message = NSString(format: "%..2f MB", mSize) as String
|
||||
let alertVC = UIAlertController(title: "Video file size", message: message, preferredStyle: .alert)
|
||||
|
||||
let alertBtn = UIAlertAction(title: "OK", style: .cancel) { (_) in
|
||||
alertVC.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
alertVC.addAction(alertBtn)
|
||||
self.present(alertVC, animated: true) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func initWriter() {
|
||||
self.movieWriter?.removeSourceAtIndex(0)
|
||||
self.movieWriter = nil
|
||||
//导出相关
|
||||
do {
|
||||
let documentsDir = try FileManager.default.url(for:.documentDirectory, in:.userDomainMask, appropriateFor:nil, create:true)
|
||||
let fileURL = URL(string:"test.mp4", relativeTo:documentsDir)!
|
||||
do {
|
||||
try FileManager.default.removeItem(at:fileURL)
|
||||
} catch {
|
||||
}
|
||||
|
||||
|
||||
let cgSize = MetalVideoProcessBackground.canvasSize
|
||||
self.movieWriter = try MetalVideoProcessMovieWriter(URL: fileURL,
|
||||
size: Size(width: Float(cgSize.width),
|
||||
height: Float(cgSize.height)),
|
||||
liveVideo: false)
|
||||
self.player?.audioEncodingTarget = nil
|
||||
self.player?.audioEncodingTarget = self.movieWriter
|
||||
//最后一个输出节点指向writer
|
||||
|
||||
|
||||
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func export(_ sender: Any) {
|
||||
self.progressHUD = MBProgressHUD.showHub(with: .loading("exporting..."), self.view, isUserInteractionEnabled: false)
|
||||
|
||||
|
||||
guard let source = self.renderView?.sources.sources[0] else {
|
||||
return
|
||||
}
|
||||
initWriter()
|
||||
source.removeAllTargets()
|
||||
source --> self.movieWriter!
|
||||
self.view.isUserInteractionEnabled = false
|
||||
|
||||
try? self.player?.startExport()
|
||||
self.movieWriter?.activateAudioTrack()
|
||||
self.movieWriter?.startRecording()
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// ViewController+UIGestureRecognizerDelegate.swift
|
||||
// SimpleVideoEditor
|
||||
//
|
||||
// Created by RenZhu Macro on 2020/7/9.
|
||||
// Copyright © 2020 RenZhu Macro. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MetalVideoProcess
|
||||
|
||||
extension ViewController: UIGestureRecognizerDelegate {
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
@IBAction func rotateAction(ges: UIRotationGestureRecognizer) {
|
||||
guard let item = (self.mainSelectedItem != nil ? self.mainSelectedItem : self.subSelectedItem) else {
|
||||
return
|
||||
}
|
||||
guard let transformFilter = item.transformFilter else {
|
||||
return
|
||||
}
|
||||
let rotDeg = transformFilter.rotate + Double(-ges.velocity)
|
||||
transformFilter.rotate = rotDeg
|
||||
item.rotate = rotDeg
|
||||
}
|
||||
|
||||
@IBAction func pinchAction(ges: UIPinchGestureRecognizer) {
|
||||
guard let item = (self.mainSelectedItem != nil ? self.mainSelectedItem : self.subSelectedItem) else {
|
||||
return
|
||||
}
|
||||
guard let transformFilter = item.transformFilter else {
|
||||
return
|
||||
}
|
||||
transformFilter.scale = Position((transformFilter.scale.x) + Float(ges.velocity * 0.01), (transformFilter.scale.y) + Float(ges.velocity * 0.01))
|
||||
var x: Float = (transformFilter.scale.x)
|
||||
var y: Float = (transformFilter.scale.y)
|
||||
if ((transformFilter.scale.x) < 0.01) {
|
||||
x = 0.01
|
||||
}
|
||||
if ((transformFilter.scale.y) < 0.01) {
|
||||
y = 0.01
|
||||
}
|
||||
transformFilter.scale = Position(x, y)
|
||||
item.scale = transformFilter.scale
|
||||
}
|
||||
|
||||
@IBAction func panAction(ges: UIPanGestureRecognizer) {
|
||||
guard let item = (self.mainSelectedItem != nil ? self.mainSelectedItem : self.subSelectedItem) else {
|
||||
return
|
||||
}
|
||||
guard let transformFilter = item.transformFilter else {
|
||||
return
|
||||
}
|
||||
|
||||
if ges.state == .changed {
|
||||
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))
|
||||
|
||||
} else if ges.state == .ended {
|
||||
currentPostion = (transformFilter.translation)
|
||||
}
|
||||
|
||||
item.translation = transformFilter.translation
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// ViewController+UIImagePickerControllerDelegate.swift
|
||||
// SimpleVideoEditor
|
||||
//
|
||||
// Created by RenZhu Macro on 2020/7/9.
|
||||
// Copyright © 2020 RenZhu Macro. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
import MobileCoreServices
|
||||
|
||||
func orientationForTrack(asset: AVAsset) -> UIInterfaceOrientation {
|
||||
let track = asset.tracks(withMediaType: .video).first
|
||||
guard let txf = track?.preferredTransform else {
|
||||
return .portrait
|
||||
}
|
||||
if txf.a == 0 && txf.b == 1.0 && txf.c == -1.0 && txf.d == 0 {
|
||||
return .landscapeRight
|
||||
} else if txf.a == 0 && txf.b == -1.0 && txf.c == 1.0 && txf.d == 0 {
|
||||
return .landscapeLeft
|
||||
} else if txf.a == 1.0 && txf.b == 0 && txf.c == 0 && txf.d == 1.0 {
|
||||
return .portrait
|
||||
} else if txf.a == -1.0 && txf.b == 0 && txf.c == 0 && txf.d == -1.0 {
|
||||
return .portraitUpsideDown
|
||||
} else {
|
||||
return .unknown
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
||||
debugPrint("info:", info)
|
||||
picker.dismiss(animated: true, completion: nil)
|
||||
guard let mediaType = info[.mediaType] else {
|
||||
return
|
||||
}
|
||||
|
||||
if mediaType as! CFString == kUTTypeMovie {
|
||||
debugPrint("video")
|
||||
guard let url = info[.mediaURL] as? URL else {
|
||||
return
|
||||
}
|
||||
|
||||
let asset = AVAsset(url: url)
|
||||
let orientation = orientationForTrack(asset: asset)
|
||||
|
||||
debugPrint("asset:", asset)
|
||||
let item = ResourceItem(asset: asset)
|
||||
|
||||
item.fillType = .aspectToFill
|
||||
item.orientation = orientation
|
||||
switch item.orientation {
|
||||
case .portrait:
|
||||
break
|
||||
case .landscapeLeft:
|
||||
item.rotate = 90.0
|
||||
break
|
||||
case .landscapeRight:
|
||||
item.rotate = -90.0
|
||||
break
|
||||
case .portraitUpsideDown:
|
||||
item.rotate = 180.0
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if picker == self.subPicker {
|
||||
try? self.videoEditor?.insertOverlayItem(overlayItem: item)
|
||||
item.roi = CGRect(x: 0.4, y: 0.4, width: 0.8, height: 0.8)
|
||||
self.subResources.append(item)
|
||||
} else {
|
||||
|
||||
try? self.videoEditor?.insertItem(videoItem: item)
|
||||
item.roi = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)
|
||||
self.mainResources.append(item)
|
||||
}
|
||||
self.rebuildPipeline()
|
||||
self.subTableView.reloadData()
|
||||
self.mainTableView.reloadData()
|
||||
|
||||
} else {
|
||||
debugPrint("unsupported")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
|
||||
|
||||
}
|
||||
|
||||
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
|
||||
|
||||
}
|
||||
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// ViewController+UITableViewDelegate.swift
|
||||
// SimpleVideoEditor
|
||||
//
|
||||
// Created by RenZhu Macro on 2020/7/9.
|
||||
// Copyright © 2020 RenZhu Macro. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MetalVideoProcess
|
||||
|
||||
extension ViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
//selected
|
||||
if tableView == self.mainTableView {
|
||||
let item = self.mainResources[indexPath.row]
|
||||
self.mainSelectedItem = item
|
||||
currentPostion = item.transformFilter?.translation ?? Position(0.0, 0.0)
|
||||
self.mainEditButton.isEnabled = true
|
||||
self.mainDeleteButton.isEnabled = true
|
||||
self.subEditButton.isEnabled = false
|
||||
self.subDeleteButton.isEnabled = false
|
||||
self.subSelectedItem = nil
|
||||
guard let subIndex = self.subTableView.indexPathForSelectedRow else {
|
||||
return
|
||||
}
|
||||
self.subTableView.deselectRow(at: subIndex, animated: true)
|
||||
|
||||
} else {
|
||||
let item = self.subResources[indexPath.row]
|
||||
self.subSelectedItem = item
|
||||
currentPostion = item.transformFilter?.translation ?? Position(0.0, 0.0)
|
||||
self.mainEditButton.isEnabled = false
|
||||
self.mainDeleteButton.isEnabled = false
|
||||
self.subEditButton.isEnabled = true
|
||||
self.subDeleteButton.isEnabled = true
|
||||
self.mainSelectedItem = nil
|
||||
guard let mainIndex = self.mainTableView.indexPathForSelectedRow else {
|
||||
return
|
||||
}
|
||||
self.mainTableView.deselectRow(at: mainIndex, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: TableViewDraggerDelegate {
|
||||
func dragger(_ dragger: TableViewDragger, moveDraggingAt indexPath: IndexPath, newIndexPath: IndexPath) -> Bool {
|
||||
if dragger == self.mainDragger! {
|
||||
let item = self.mainResources[indexPath.row]
|
||||
self.mainResources.remove(at: indexPath.row)
|
||||
self.mainResources.insert(item, at: newIndexPath.row)
|
||||
mainTableView.moveRow(at: indexPath, to: newIndexPath)
|
||||
} else {
|
||||
let item = self.subResources[indexPath.row]
|
||||
self.subResources.remove(at: indexPath.row)
|
||||
self.subResources.insert(item, at: newIndexPath.row)
|
||||
subTableView.moveRow(at: indexPath, to: newIndexPath)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func dragger(_ dragger: TableViewDragger, didEndDraggingAt indexPath: IndexPath) {
|
||||
dragger.tableView?.reloadData()
|
||||
}
|
||||
|
||||
func dragger(_ dragger: TableViewDragger, willEndDraggingAt indexPath: IndexPath) {
|
||||
self.rebuildPipeline()
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: TableViewDraggerDataSource {
|
||||
|
||||
}
|
||||
|
||||
extension ViewController: UITableViewDataSource {
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
if self.mainTableView == tableView {
|
||||
return self.mainResources.count
|
||||
} else {
|
||||
return self.subResources.count
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
if self.mainTableView == tableView {
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "mainCell", for: indexPath) as? ResourceItemTableViewCell else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
let item = self.mainResources[indexPath.row]
|
||||
cell.textLabel?.text = NSString(format: "t:%d s:%@ d:%@", item.trackID, item.startTimeText, item.durationText) as String
|
||||
return cell
|
||||
} else {
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "pipCell", for: indexPath) as? ResourceItemTableViewCell else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
let item = self.subResources[indexPath.row]
|
||||
cell.textLabel?.text = NSString(format: "t:%d s:%@ d:%@", item.trackID, item.startTimeText, item.durationText) as String
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,17 @@ class ViewController: UIViewController {
|
||||
|
||||
var grayFilter: MetalVideoProcessLuminance?
|
||||
|
||||
public var transition: MetalVideoProcessTransition? = MetalVideoProcessFadeTransition()
|
||||
deinit {
|
||||
|
||||
print(" ViewController deinit")
|
||||
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
guard let transition = self.transition else {
|
||||
return
|
||||
}
|
||||
super.viewDidLoad()
|
||||
let asset1 = AVAsset(url: Bundle.main.url(forResource: "853", withExtension: "mp4")!)
|
||||
let asset2 = AVAsset(url: Bundle.main.url(forResource: "cute", withExtension: "mp4")!)
|
||||
@@ -34,6 +44,8 @@ class ViewController: UIViewController {
|
||||
let transitionDuration = CMTime.init(seconds: 2.0, preferredTimescale: 600)
|
||||
item1.videoTransition = TransitionDuration(duration: transitionDuration)
|
||||
item1.audioTransition = FadeInOutAudioTransition(duration: transitionDuration)
|
||||
|
||||
|
||||
do {
|
||||
let editor = try MetalVideoEditor(videoItems: [item1, item2],
|
||||
customVideoCompositorClass: MetalVideoProcessCompositor.self)
|
||||
@@ -41,61 +53,38 @@ class ViewController: UIViewController {
|
||||
let playerItem = editor.buildPlayerItem()
|
||||
self.progress.maximumValue = Float(playerItem.duration.seconds)
|
||||
let player = try MetalVideoProcessPlayer(playerItem: playerItem)
|
||||
|
||||
let beautyFilter1 = MetalVideoProcessBeautyFilter()
|
||||
beautyFilter1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID)
|
||||
beautyFilter1.isEnable = false
|
||||
|
||||
let beautyFilter2 = MetalVideoProcessBeautyFilter()
|
||||
beautyFilter2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID)
|
||||
beautyFilter2.isEnable = false
|
||||
|
||||
let blurFilter1 = MetalVideoProcessGaussianBlurFilter()
|
||||
blurFilter1.saveUniformSettings(forTimelineRange: item1.timeRange, trackID: item1.trackID)
|
||||
blurFilter1.isEnable = false
|
||||
|
||||
let blurFilter2 = MetalVideoProcessGaussianBlurFilter()
|
||||
blurFilter2.saveUniformSettings(forTimelineRange: item2.timeRange, trackID: item2.trackID)
|
||||
blurFilter2.isEnable = false
|
||||
|
||||
let gray = MetalVideoProcessLuminance()
|
||||
gray.isEnable = false
|
||||
self.grayFilter = gray
|
||||
|
||||
self.beauty1 = beautyFilter1
|
||||
self.beauty2 = beautyFilter2
|
||||
|
||||
self.blur1 = blurFilter1
|
||||
self.blur2 = blurFilter2
|
||||
|
||||
let transitionTimeRange = item1.timeRange.intersection(item2.timeRange)
|
||||
let fadeTransition = MetalVideoProcessFadeTransition()
|
||||
|
||||
|
||||
//注意顺序 第一个视频在前 第二视频在后
|
||||
fadeTransition.mainTrackIDs.append(item1.trackID)
|
||||
fadeTransition.mainTrackIDs.append(item2.trackID)
|
||||
transition.mainTrackIDs.append(item1.trackID)
|
||||
transition.mainTrackIDs.append(item2.trackID)
|
||||
|
||||
//告知转场的时间 通过item1和item2的intersection计算
|
||||
fadeTransition.saveUniformSettings(forTimelineRange: transitionTimeRange, trackID: 0)
|
||||
item1.transitoin = fadeTransition
|
||||
transition.saveUniformSettings(forTimelineRange: transitionTimeRange, trackID: 0)
|
||||
item1.transitoin = transition
|
||||
|
||||
//Begin build pipeline
|
||||
player.addTarget(beautyFilter1, atTargetIndex: nil, trackID: item1.trackID, targetTrackId: 0)
|
||||
player.addTarget(beautyFilter2, atTargetIndex: nil, trackID: item2.trackID, targetTrackId: item2.trackID)
|
||||
//mapping trackId on mainTrack 0
|
||||
|
||||
beautyFilter1 --> blurFilter1 --> fadeTransition
|
||||
beautyFilter2 --> blurFilter2 --> fadeTransition --> gray --> renderView
|
||||
//Done
|
||||
|
||||
player.addTarget(transition, atTargetIndex: nil, trackID: item1.trackID, targetTrackId: 0)
|
||||
player.addTarget(transition, atTargetIndex: nil, trackID: item2.trackID, targetTrackId: item2.trackID)
|
||||
|
||||
transition --> renderView
|
||||
|
||||
self.player = player
|
||||
self.player?.playerDelegate = self
|
||||
|
||||
self.player?.play()
|
||||
} catch {
|
||||
debugPrint("init error")
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
// player?.suspend()
|
||||
player?.dispose()
|
||||
// player?.removeAllTargets()
|
||||
// player = nil
|
||||
}
|
||||
|
||||
@IBAction func play(_ sender: Any) {
|
||||
self.player?.play()
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user