4 Commits

Author SHA1 Message Date
RenZhu Macro bd7ee080bd Add video animation support, update examples SimpleVideoAnimation SimpleVideoTransitionSwitch 2020-07-22 16:02:13 +08:00
GhostZephyr 3929205637 Merge pull request #1 from wangrenzhu/add-license-1
Create LICENSE
2020-07-21 10:34:54 +08:00
GhostZephyr 13f860cc37 Create LICENSE 2020-07-21 10:34:15 +08:00
GhostZephyr 3c853abfe0 Update README.md 2020-07-20 15:46:28 +08:00
145 changed files with 10339 additions and 72 deletions
+5
View File
@@ -7,3 +7,8 @@ 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
@@ -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
+11
View File
@@ -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
View File
@@ -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.
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
}
@@ -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()
}
}
@@ -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)
}
}
@@ -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
[![codebeat badge](https://codebeat.co/badges/ac008534-7f30-4b04-8434-0c6d69251e4b)](https://codebeat.co/projects/github-com-prynt-prynttrimmerview-master)
[![Platform](https://img.shields.io/cocoapods/p/PryntTrimmerView.svg?style=flat)](http://cocoapods.org/pods/PryntTrimmerView)
[![License](https://img.shields.io/cocoapods/l/PryntTrimmerView.svg?style=flat)](http://cocoapods.org/pods/PryntTrimmerView)
[![Version](https://img.shields.io/cocoapods/v/PryntTrimmerView.svg?style=flat)](http://cocoapods.org/pods/PryntTrimmerView)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](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
![](https://media.giphy.com/media/GwZGkLiKxZcTm/giphy.gif)
### Cropping
![](https://media.giphy.com/media/10FsDfHS7616XC/giphy.gif)
## 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.
@@ -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>
@@ -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
@@ -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>
@@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_Pods_SimpleVideoAnimation : NSObject
@end
@implementation PodsDummy_Pods_SimpleVideoAnimation
@end
@@ -0,0 +1,2 @@
${PODS_ROOT}/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks.sh
${BUILT_PRODUCTS_DIR}/PryntTrimmerView/PryntTrimmerView.framework
@@ -0,0 +1 @@
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PryntTrimmerView.framework
@@ -0,0 +1,2 @@
${PODS_ROOT}/Target Support Files/Pods-SimpleVideoAnimation/Pods-SimpleVideoAnimation-frameworks.sh
${BUILT_PRODUCTS_DIR}/PryntTrimmerView/PryntTrimmerView.framework
@@ -0,0 +1 @@
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PryntTrimmerView.framework
@@ -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
@@ -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[];
@@ -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
@@ -0,0 +1,6 @@
framework module Pods_SimpleVideoAnimation {
umbrella header "Pods-SimpleVideoAnimation-umbrella.h"
export *
module * { export * }
}
@@ -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
@@ -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>
@@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_PryntTrimmerView : NSObject
@end
@implementation PodsDummy_PryntTrimmerView
@end
@@ -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
@@ -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[];
@@ -0,0 +1,6 @@
framework module PryntTrimmerView {
umbrella header "PryntTrimmerView-umbrella.h"
export *
module * { export * }
}
@@ -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,477 @@
// !$*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 */; };
/* 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>"; };
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 */,
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 */,
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 */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:SimpleVideoAnimation.xcodeproj">
</FileRef>
</Workspace>
@@ -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>
@@ -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>
@@ -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.
}
}
@@ -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,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<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="tne-QT-ifu">
<objects>
<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" translatesAutoresizingMaskIntoConstraints="NO" id="FXj-LY-59h" customClass="MetalVideoProcessRenderView" customModule="MetalVideoProcess">
<rect key="frame" x="0.0" y="0.0" width="375" height="375"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstAttribute="width" secondItem="FXj-LY-59h" secondAttribute="height" id="iBP-Gu-Wj9"/>
</constraints>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gLe-Zn-niL">
<rect key="frame" x="166.5" y="515" 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" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9lP-KN-D4J">
<rect key="frame" x="172.5" y="475" 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" translatesAutoresizingMaskIntoConstraints="NO" id="JkB-hg-7A6" customClass="TrimmerView" customModule="PryntTrimmerView">
<rect key="frame" x="0.0" y="385" width="375" height="80"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstAttribute="height" constant="80" id="9zr-ar-gfx"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<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>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</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="138"/>
</scene>
</scenes>
</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,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,130 @@
//
// 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?
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 = MetalVideoProcessFadeInMotion()
fadeIn.timingType = .quadraticEaseOut
let fadeOut = MetalVideoProcessFadeOutMotion()
fadeOut.timingType = .quarticEaseOut
let fadeInTimeRange = CMTimeRangeMake(start: CMTime(seconds: 2.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.25, y: 0.25, width: 0.5, height: 0.5)
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 //source 1
background --> fadeOut //source 0
fadeIn --> fadeOut --> 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
}
@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) {
}
}
@@ -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>
@@ -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>
@@ -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
@@ -1,5 +1,5 @@
<?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="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina5_9" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
@@ -10,7 +10,7 @@
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="SimpleRealtimeFilterPlayback" customModuleProvider="target" sceneMemberID="viewController">
<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"/>
@@ -0,0 +1,37 @@
//
// AppDelegate.swift
// SimpleVideoTransition
//
// Created by RenZhu Macro on 2020/7/2.
// 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.
}
}
@@ -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,158 @@
<?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="BYZ-38-t0r">
<device id="retina5_9" 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 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"/>
<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"/>
<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="system" 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"/>
<connections>
<action selector="play:" destination="BYZ-38-t0r" eventType="touchUpInside" id="cZ4-2E-l3J"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Blur track1:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ieB-aM-Q6C">
<rect key="frame" x="20" y="663.66666666666663" width="108" height="21"/>
<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"/>
<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="system" 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"/>
</connections>
</button>
</subviews>
<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="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="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"/>
</constraints>
</view>
<connections>
<outlet property="progress" destination="vYk-6H-dKP" id="qNJ-VV-aFL"/>
<outlet property="renderView" destination="Dc5-Dz-opT" id="LzR-68-614"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="130.40000000000001" y="137.4384236453202"/>
</scene>
</scenes>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "baseline_camera_alt_black_24pt_1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "baseline_camera_alt_black_24pt_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "baseline_camera_alt_black_24pt_3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
@@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "baseline_cloud_black_24pt_1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "baseline_cloud_black_24pt_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "baseline_cloud_black_24pt_3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
@@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "baseline_photo_library_black_24pt_1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "baseline_photo_library_black_24pt_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "baseline_photo_library_black_24pt_3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
@@ -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,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,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
}
}
@@ -0,0 +1,53 @@
//
// SceneDelegate.swift
// SimpleVideoTransition
//
// Created by RenZhu Macro on 2020/7/2.
// 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,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"
@@ -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
}
}
@@ -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()
}
}
@@ -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)
}
}
@@ -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
}
}
}
@@ -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>
@@ -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()
}
}
@@ -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 ""
}
}
}
@@ -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)
}
}
}
@@ -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
}
}
@@ -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()
}
}
@@ -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()
}
}
@@ -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)
}
}
}
@@ -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)
}
}
@@ -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()
}
}
@@ -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)
}
}
@@ -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()
}
}
@@ -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 }
}
}
@@ -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()
}
}
@@ -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
}
}
@@ -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)
})
}
}
@@ -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
}
}
}

Some files were not shown because too many files have changed in this diff Show More