diff --git a/CESDK-Showcases.xcodeproj/project.pbxproj b/CESDK-Showcases.xcodeproj/project.pbxproj index ad1ae9f..f09a71c 100644 --- a/CESDK-Showcases.xcodeproj/project.pbxproj +++ b/CESDK-Showcases.xcodeproj/project.pbxproj @@ -110,6 +110,7 @@ 8F8632307F19C9450B417E74 /* DesignEditorSolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E8CC37ABF7FEDFE48C2441 /* DesignEditorSolution.swift */; }; 916AB1E6EA74B780AA220E09 /* 9_16_dark.png in Resources */ = {isa = PBXBuildFile; fileRef = 35314E6375DFA482337CA470 /* 9_16_dark.png */; }; 92ADF7F1DA99D4AC18CD1DDA /* x_profile_picture.png in Resources */ = {isa = PBXBuildFile; fileRef = A5F43ADC1DF9B3F55DA69E92 /* x_profile_picture.png */; }; + 9308866C3298BBBF53678AD7 /* ModalCameraShowcase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A556562136063EF09820B432 /* ModalCameraShowcase.swift */; }; 93A281249972085593F51AF1 /* SaveSceneToString.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F28E94E9E5E1FDD880847B /* SaveSceneToString.swift */; }; 94F7406B182A697C6CA92929 /* CreateSceneFromVideoURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000B20D1152A1EE4183F2118 /* CreateSceneFromVideoURL.swift */; }; 97B5226CB1A792235BF57C02 /* instagram_story_dark.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D77999E522561716BC615F1 /* instagram_story_dark.png */; }; @@ -282,6 +283,7 @@ A2B180D5F50DFED3C8A44081 /* apparel-ui-b-3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "apparel-ui-b-3.png"; sourceTree = ""; }; A3DB715E1405C2F87FA79968 /* LoadSceneFromRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadSceneFromRemote.swift; sourceTree = ""; }; A444D6F3EA290E51BE49CEE7 /* CreateSceneFromScratch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSceneFromScratch.swift; sourceTree = ""; }; + A556562136063EF09820B432 /* ModalCameraShowcase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalCameraShowcase.swift; sourceTree = ""; }; A5C88D0A9F71C0ED16DC15F8 /* Assets.bundle */ = {isa = PBXFileReference; lastKnownFileType = wrapper.cfbundle; path = Assets.bundle; sourceTree = ""; }; A5F43ADC1DF9B3F55DA69E92 /* x_profile_picture.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = x_profile_picture.png; sourceTree = ""; }; A606B83A49C463028B8A3147 /* 4_3_dark.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 4_3_dark.png; sourceTree = ""; }; @@ -656,6 +658,7 @@ 732FF6CB9072C468F6E078EA /* View */ = { isa = PBXGroup; children = ( + A556562136063EF09820B432 /* ModalCameraShowcase.swift */, 61BFC53599B9618C1A521532 /* ModalEditorLink.swift */, E7EB80DFE06724B15D95EF84 /* PhotoSelection.swift */, DB0BBF363BE528CED8BA335F /* SceneSelection.swift */, @@ -1036,7 +1039,7 @@ path = "engine-guides-uri-resolver"; sourceTree = ""; }; - "TEMP_2F0953B5-C036-4DAE-B35B-CC0FA3E7D392" /* cesdk_swift_examples */ = { + "TEMP_4C64C1E8-CA82-417B-911F-A175A2C79C4A" /* cesdk_swift_examples */ = { isa = PBXGroup; children = ( 582BF90249D09687E7CFACDC /* editor-guides-solutions-video-editor */, @@ -1343,6 +1346,7 @@ 8F8632307F19C9450B417E74 /* DesignEditorSolution.swift in Sources */, DD4DB7F359039FB702431E12 /* EditorSwiftUI.swift in Sources */, 125753B6088EE43A454047D1 /* EditorUIKit.swift in Sources */, + 9308866C3298BBBF53678AD7 /* ModalCameraShowcase.swift in Sources */, CE57A59EE2CFA43BE858D495 /* ModalEditor.swift in Sources */, 1DB1824BCEDB0938F56C7F98 /* ModalEditorLink.swift in Sources */, D4BB01543B05839D48F77680 /* PhotoEditorSolution.swift in Sources */, @@ -1462,7 +1466,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; - MARKETING_VERSION = 1.36.1; + MARKETING_VERSION = 1.37.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1552,7 +1556,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; - MARKETING_VERSION = 1.36.1; + MARKETING_VERSION = 1.37.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1627,7 +1631,7 @@ repositoryURL = "https://github.com/imgly/IMGLYUI-swift"; requirement = { kind = exactVersion; - version = 1.36.1; + version = 1.37.0; }; }; 10597E39A033ABD8B233CD14 /* XCRemoteSwiftPackageReference "IMGLYEngine-swift" */ = { @@ -1635,7 +1639,7 @@ repositoryURL = "https://github.com/imgly/IMGLYEngine-swift"; requirement = { kind = exactVersion; - version = 1.36.1; + version = 1.37.0; }; }; 150F161C090426538C39E173 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { diff --git a/camera-guides-configuration/ConfiguredCameraSolution.swift b/camera-guides-configuration/ConfiguredCameraSolution.swift index b625c7c..4199b1b 100644 --- a/camera-guides-configuration/ConfiguredCameraSolution.swift +++ b/camera-guides-configuration/ConfiguredCameraSolution.swift @@ -23,14 +23,21 @@ struct ConfiguredCameraSolution: View { // highlight-highlightcolor highlightColor: .yellow, // highlight-maxtotalduration - maxTotalDuration: 30 + maxTotalDuration: 30, + // highlight-allowmodeswitching + allowModeSwitching: true ) // highlight-config - Camera(settings, config: config) { result in + Camera( + settings, + config: config, + // highlight-mode + mode: .standard + ) { result in switch result { - case let .success(recordings): - print(recordings) + case let .success(cameraResult): + print(cameraResult) case let .failure(error): print(error.localizedDescription) isPresented = false diff --git a/camera-guides-quickstart/CameraSwiftUI.swift b/camera-guides-quickstart/CameraSwiftUI.swift index b6408a1..fafb34b 100644 --- a/camera-guides-quickstart/CameraSwiftUI.swift +++ b/camera-guides-quickstart/CameraSwiftUI.swift @@ -23,11 +23,15 @@ struct CameraSwiftUI: View { // highlight-initialization // highlight-result switch result { - case let .success(recordings): + case let .success(.recording(recordings)): let urls = recordings.flatMap { $0.videos.map(\.url) } let recordedVideos = urls // Do something with the recorded videos print(recordedVideos) + + case .success(.reaction): + print("Reaction case not handled here") + case let .failure(error): print(error.localizedDescription) isPresented = false diff --git a/camera-guides-quickstart/CameraUIKit.swift b/camera-guides-quickstart/CameraUIKit.swift index a32c276..5bb7b6e 100644 --- a/camera-guides-quickstart/CameraUIKit.swift +++ b/camera-guides-quickstart/CameraUIKit.swift @@ -13,11 +13,15 @@ class CameraUIKit: UIViewController { // highlight-initialization // highlight-result switch result { - case let .success(recordings): + case let .success(.recording(recordings)): let urls = recordings.flatMap { $0.videos.map(\.url) } let recordedVideos = urls // Do something with the recorded videos print(recordedVideos) + + case .success(.reaction): + print("Reaction case not handled here") + case let .failure(error): print(error.localizedDescription) self.presentedViewController?.dismiss(animated: true) diff --git a/camera-guides-recordings/RecordingsCameraSolution.swift b/camera-guides-recordings/RecordingsCameraSolution.swift index b351150..576287f 100644 --- a/camera-guides-recordings/RecordingsCameraSolution.swift +++ b/camera-guides-recordings/RecordingsCameraSolution.swift @@ -15,7 +15,8 @@ struct RecordingsCameraSolution: View { Camera(settings) { result in switch result { // highlight-success - case let .success(recordings): + // highlight-standard + case let .success(.recording(recordings)): for recording in recordings { print(recording.duration) for video in recording.videos { @@ -23,13 +24,27 @@ struct RecordingsCameraSolution: View { print(video.rect) } } - // highlight-success + // highlight-standard + + // highlight-reaction + case let .success(.reaction(video, reactionRecordings)): + print(video.duration) + for recording in reactionRecordings { + print(recording.duration) + for video in recording.videos { + print(video.url) + print(video.rect) + } + } + // highlight-reaction + // highlight-success + // highlight-failure case let .failure(error): print(error.localizedDescription) isPresented = false + // highlight-failure } - // highlight-failure } } } diff --git a/editor-guides-configuration-basics/BasicEditorSolution.swift b/editor-guides-configuration-basics/BasicEditorSolution.swift index 4ad144b..2ccb2d4 100644 --- a/editor-guides-configuration-basics/BasicEditorSolution.swift +++ b/editor-guides-configuration-basics/BasicEditorSolution.swift @@ -8,7 +8,7 @@ struct BasicEditorSolution: View { // highlight-userID userID: "", // highlight-baseURL - baseURL: URL(string: "https://cdn.img.ly/packages/imgly/cesdk-engine/1.36.1/assets")! + baseURL: URL(string: "https://cdn.img.ly/packages/imgly/cesdk-engine/1.37.0/assets")! ) var editor: some View { diff --git a/engine-guides-exporting-blocks/ExportingBlocks.swift b/engine-guides-exporting-blocks/ExportingBlocks.swift index b0835be..53c352f 100644 --- a/engine-guides-exporting-blocks/ExportingBlocks.swift +++ b/engine-guides-exporting-blocks/ExportingBlocks.swift @@ -9,7 +9,7 @@ import IMGLYEngine @MainActor func exportingBlocks(engine: Engine) async throws { - try engine.editor.setSettingString("basePath", value: "https://cdn.img.ly/packages/imgly/cesdk-engine/1.36.1/assets") + try engine.editor.setSettingString("basePath", value: "https://cdn.img.ly/packages/imgly/cesdk-engine/1.37.0/assets") try await engine.addDefaultAssetSources() let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! diff --git a/engine-guides-load-scene-from-blob/LoadSceneFromBlob.swift b/engine-guides-load-scene-from-blob/LoadSceneFromBlob.swift index 4b15905..56147ed 100644 --- a/engine-guides-load-scene-from-blob/LoadSceneFromBlob.swift +++ b/engine-guides-load-scene-from-blob/LoadSceneFromBlob.swift @@ -10,7 +10,7 @@ func loadSceneFromBlob(engine: Engine) async throws { // highlight-fetch-blob // highlight-read-blob - let blobString = String(decoding: sceneBlob, as: UTF8.self) + let blobString = String(data: sceneBlob, encoding: .utf8)! // highlight-read-blob // highlight-load diff --git a/engine-guides-load-scene-from-string/LoadSceneFromString.swift b/engine-guides-load-scene-from-string/LoadSceneFromString.swift index 5f9cc70..404f8a7 100644 --- a/engine-guides-load-scene-from-string/LoadSceneFromString.swift +++ b/engine-guides-load-scene-from-string/LoadSceneFromString.swift @@ -7,7 +7,7 @@ func loadSceneFromString(engine: Engine) async throws { let sceneURL = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! let sceneBlob = try await URLSession.shared.data(from: sceneURL).0 - let blobString = String(decoding: sceneBlob, as: UTF8.self) + let blobString = String(data: sceneBlob, encoding: .utf8)! // highlight-fetch-string // highlight-load diff --git a/engine-guides-store-metadata/StoreMetadata.swift b/engine-guides-store-metadata/StoreMetadata.swift index ecf8d6a..e4c4e09 100644 --- a/engine-guides-store-metadata/StoreMetadata.swift +++ b/engine-guides-store-metadata/StoreMetadata.swift @@ -25,7 +25,7 @@ func storeMetadata(engine: Engine) async throws { try engine.block.setMetadata( block, key: "payment", - value: String(decoding: JSONEncoder().encode(payment), as: UTF8.self) + value: String(data: JSONEncoder().encode(payment), encoding: .utf8)! ) // highlight-setMetadata diff --git a/engine-guides-text-properties/TextProperties.swift b/engine-guides-text-properties/TextProperties.swift index fef3719..b6fe1e2 100644 --- a/engine-guides-text-properties/TextProperties.swift +++ b/engine-guides-text-properties/TextProperties.swift @@ -82,6 +82,10 @@ func textProperties(engine: Engine) async throws { try engine.block.setFont(text, fontFileURL: typeface.fonts[3].uri, typeface: typeface) // highlight-setFont + // highlight-setTypeface + try engine.block.setTypeface(text, fallbackFontFileURL: typeface.fonts[3].uri, typeface: typeface) + // highlight-setTypeface + // highlight-getTypeface let currentTypeface = try engine.block.getTypeface(text) // highlight-getTypeface diff --git a/showcases-app/View/Examples/CameraShowcase.swift b/showcases-app/View/Examples/CameraShowcase.swift index e52250e..1ccafef 100644 --- a/showcases-app/View/Examples/CameraShowcase.swift +++ b/showcases-app/View/Examples/CameraShowcase.swift @@ -11,13 +11,20 @@ struct CameraShowcase: ViewModifier { .fullScreenCover(isPresented: $isCameraSheetShown) { Camera(settings) { result in switch result { - case let .success(recordings): - let urls = recordings.flatMap { $0.videos.map(\.url) } - let recordedVideos = urls - shareItem = .url(recordedVideos) + case let .failure(error) where error == .cancelled: + isCameraSheetShown = false + case let .failure(error): print(error.localizedDescription) isCameraSheetShown = false + + case let .success(.recording(recordings)): + let urls = recordings.flatMap { $0.videos.map(\.url) } + let recordedVideos = urls + shareItem = .url(recordedVideos) + + case .success(.reaction): + print("Reaction case not handled here") } } .imgly.shareSheet(item: $shareItem) { diff --git a/showcases-app/View/ModalCameraShowcase.swift b/showcases-app/View/ModalCameraShowcase.swift new file mode 100644 index 0000000..8be17ec --- /dev/null +++ b/showcases-app/View/ModalCameraShowcase.swift @@ -0,0 +1,62 @@ +import IMGLYCamera +import IMGLYVideoEditor +import SwiftUI + +struct ModalCameraShowcase: View { + let title: LocalizedStringKey + let subtitle: LocalizedStringKey? + let mode: CameraMode + + struct CameraResultWrapper: Identifiable { + let id = UUID() + let result: CameraResult + } + + @ViewBuilder private var label: some View { + VStack(alignment: .leading, spacing: 5) { + Text(title) + if let subtitle { + Text(subtitle).font(.footnote) + } + } + } + + @State private var isCameraPresented = false + @State var result: CameraResultWrapper? + + var body: some View { + Button { + isCameraPresented = true + } label: { + label + } + .fullScreenCover(isPresented: $isCameraPresented) { + Camera( + settings, + config: CameraConfiguration(allowModeSwitching: false), + mode: mode + ) { result in + switch result { + case let .failure(error) where error == .cancelled: + isCameraPresented = false + + case let .failure(error): + print(error.localizedDescription) + isCameraPresented = false + + case let .success(result): + self.result = CameraResultWrapper(result: result) + isCameraPresented = false + } + } + } + .fullScreenCover(item: $result) { result in + ModalEditor { + VideoEditor(settings) + .imgly.onCreate { engine in + try await OnCreate.loadVideos(from: result.result)(engine) + } + } + } + } +} diff --git a/showcases-app/View/Showcases.swift b/showcases-app/View/Showcases.swift index f933c1d..f8d653a 100644 --- a/showcases-app/View/Showcases.swift +++ b/showcases-app/View/Showcases.swift @@ -47,6 +47,23 @@ struct Showcases: View { title: "Custom Video Editor", subtitle: "Custom video scene and adds Unsplash asset source and library." ) + ModalCameraShowcase( + title: "React to Video", + // swiftlint:disable:next line_length + subtitle: "Loads a video into the camera that plays along while recording, then shows the result in the editor.", + mode: .reaction( + .vertical, + video: URL( + // swiftlint:disable:next line_length + string: "https://cdn.img.ly/assets/demo/v2/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4" + )! + ) + ) + ModalCameraShowcase( + title: "Dual Camera to Video Editor", + subtitle: "Shows the dual camera, then imports all the recorded clips into the editor.", + mode: .dualCamera() + ) } Section(title: "Apparel Editor", subtitle: "Customize and export a print-ready design with a mobile apparel editor.") {