In this example, we will show you how to customize the asset library for the mobile editor. The example is based on the Design Editor
, however, it is exactly the same for all the other solutions.
Explore a full code sample on GitHub.
Modifiers
After initializing an editor SwiftUI view you can apply any SwiftUI modifier to customize it like for any other SwiftUI view.
All public Swift extension
s of existing types provided by IMG.LY, e.g., for the SwiftUI View
protocol, are exposed in a separate .imgly
property namespace.
The asset library configuration to customize the editor is no exception to this rule and is implemented as a SwiftUI modifier.
DesignEditor(settings)
assetLibrary
- the asset library UI definition used by the editor. The result of the trailing closure needs to conform to theAssetLibrary
protocol. By default, the predefinedDefaultAssetLibrary
is used.
.imgly.assetLibrary { CustomAssetLibrary()}
Custom Asset Source
To use custom asset sources in the asset library UI, the custom asset source must be first added to the engine. In addition to creating or loading a scene, registering the asset sources should be done in the onCreate
callback. In this example, the OnCreate.loadScene
default implementation is used and afterward, the custom UnsplashAssetSource
is added.
.imgly.onCreate { engine in try await OnCreate.loadScene(from: DesignEditor.defaultScene)(engine) try engine.asset.addSource(UnsplashAssetSource(host: secrets.unsplashHost))}
Default Asset Library
The DefaultAssetLibrary
is a predefined AssetLibrary
intended to quickly customize some parts of the default asset library without implementing a complete AssetLibrary
from scratch. It can be initialized with a custom selection and ordering of the available tabs. In this example, we reverse the ordering and exclude the elements and uploads tab.
DefaultAssetLibrary( tabs: DefaultAssetLibrary.Tab.allCases.reversed().filter { tab in tab != .elements && tab != .uploads })
Asset Library Builder
The content of some of the tabs can be changed with modifiers that are defined on the DefaultAssetLibrary
type and expect a trailing @AssetLibraryBuilder
closure similar to regular SwiftUI @ViewBuilder
closures. These type-bound modifiers are videos
, audio
, images
, shapes
, and stickers
. The elements tab will then be generated with these definitions. In this example, we reuse the DefaultAssetLibrary.images
default implementation and add a new AssetLibrarySource
for the previously added UnsplashAssetSource
which will add a new “Unsplash” section to the asset library UI.
.images { AssetLibrarySource.image(.title("Unsplash"), source: .init(id: UnsplashAssetSource.id)) DefaultAssetLibrary.images}
Custom Asset Library
If the DefaultAssetLibrary
is not customizable enough for your use case you can create your own custom AssetLibrary
.
.imgly.assetLibrary { CustomAssetLibrary()}
In this example, we did exactly that by creating the CustomAssetLibrary
. It resembles the above customized DefaultAssetLibrary
with the added UnsplashAssetSource
but without the custom tab configuration which is not needed as you control every section, layout, and grouping.
import IMGLYEditorimport IMGLYEngineimport SwiftUI
@MainActorstruct CustomAssetLibrary: AssetLibrary {
As used above for customizing the DefaultAssetLibrary
with its modifiers, the @AssetLibraryBuilder
concept is the foundation to quickly create any asset library hierarchy. It behaves and feels like the regular SwiftUI @ViewBuilder
syntax. You compose your asset library out of AssetLibrarySource
s that can be organized in named AssetLibraryGroup
s. There are different flavors of these two for each asset type which define the used asset preview and section styling.
@AssetLibraryBuilder func uploads(_ sceneMode: SceneMode?) -> AssetLibraryContent { AssetLibrarySource.imageUpload(.title("Images"), source: .init(demoSource: .imageUpload)) if sceneMode == .video { AssetLibrarySource.videoUpload(.title("Videos"), source: .init(demoSource: .videoUpload)) } }
@AssetLibraryBuilder var videosAndImages: AssetLibraryContent { AssetLibraryGroup.video("Videos") { videos } AssetLibraryGroup.image("Images") { images } AssetLibraryGroup.upload("Photo Roll") { AssetLibrarySource.imageUpload(.title("Images"), source: .init(demoSource: .imageUpload)) AssetLibrarySource.videoUpload(.title("Videos"), source: .init(demoSource: .videoUpload)) } }
@AssetLibraryBuilder var videos: AssetLibraryContent { AssetLibrarySource.video(.title("Videos"), source: .init(demoSource: .video)) AssetLibrarySource.videoUpload(.title("Photo Roll"), source: .init(demoSource: .videoUpload)) }
@AssetLibraryBuilder var audio: AssetLibraryContent { AssetLibrarySource.audio(.title("Audio"), source: .init(demoSource: .audio)) AssetLibrarySource.audioUpload(.title("Uploads"), source: .init(demoSource: .audioUpload)) }
@AssetLibraryBuilder var images: AssetLibraryContent { AssetLibrarySource.image(.title("Unsplash"), source: .init(id: UnsplashAssetSource.id)) AssetLibrarySource.image(.title("Images"), source: .init(demoSource: .image)) AssetLibrarySource.imageUpload(.title("Photo Roll"), source: .init(demoSource: .imageUpload)) }
let text = AssetLibrarySource.text(.title("Text"), source: .init(id: TextAssetSource.id))
@AssetLibraryBuilder public var textAndTextComponents: AssetLibraryContent { AssetLibrarySource.text(.title("Plain Text"), source: .init(id: TextAssetSource.id)) AssetLibrarySource.textComponent(.title("Font Combinations"), source: .init(demoSource: .textComponents)) }
@AssetLibraryBuilder var shapes: AssetLibraryContent { AssetLibrarySource.shape(.title("Basic"), source: .init( defaultSource: .vectorPath, config: .init(groups: ["//ly.img.cesdk.vectorpaths/category/vectorpaths"]))) AssetLibrarySource.shape(.title("Abstract"), source: .init( defaultSource: .vectorPath, config: .init(groups: ["//ly.img.cesdk.vectorpaths.abstract/category/abstract"]))) }
@AssetLibraryBuilder var stickers: AssetLibraryContent { AssetLibrarySource.sticker(.titleForGroup { group in if let name = group?.split(separator: "/").last { name.capitalized } else { "Stickers" } }, source: .init(defaultSource: .sticker)) }
@AssetLibraryBuilder func elements(_ sceneMode: SceneMode?) -> AssetLibraryContent { AssetLibraryGroup.upload("Photo Roll") { uploads(sceneMode) } if sceneMode == .video { AssetLibraryGroup.video("Videos") { videos } AssetLibraryGroup.audio("Audio") { audio } } AssetLibraryGroup.image("Images") { images } if sceneMode == .video { text } else { AssetLibraryGroup.text("Text", excludedPreviewSources: [Engine.DemoAssetSource.textComponents.rawValue]) { textAndTextComponents } } AssetLibraryGroup.shape("Shapes") { shapes } AssetLibraryGroup.sticker("Stickers") { stickers } }
To compose a SwiftUI view for any AssetLibraryBuilder
result you use an AssetLibraryTab
which can be added to your view hierarchy. In this example, we reuse the labels defined in the DefaultAssetLibrary
but you can also use your own SwiftUI Label
or any other view. The argument of the label
closure just forwards the title that was used to initialize the AssetLibraryTab
so that you don’t have to type it twice.
@ViewBuilder var uploadsTab: some View { AssetLibrarySceneModeReader { sceneMode in AssetLibraryTab("Photo Roll") { uploads(sceneMode) } label: { DefaultAssetLibrary.uploadsLabel($0) } } }
@ViewBuilder var elementsTab: some View { AssetLibrarySceneModeReader { sceneMode in AssetLibraryTab("Elements") { elements(sceneMode) } label: { DefaultAssetLibrary.elementsLabel($0) } } }
@ViewBuilder var videosTab: some View { AssetLibraryTab("Videos") { videos } label: { DefaultAssetLibrary.videosLabel($0) } }
@ViewBuilder var audioTab: some View { AssetLibraryTab("Audio") { audio } label: { DefaultAssetLibrary.audioLabel($0) } }
@ViewBuilder var imagesTab: some View { AssetLibraryTab("Images") { images } label: { DefaultAssetLibrary.imagesLabel($0) } }
@ViewBuilder var textTab: some View { AssetLibrarySceneModeReader { sceneMode in if sceneMode == .video { AssetLibraryTabView("Text") { text.content } label: { DefaultAssetLibrary.textLabel($0) } } else { AssetLibraryTab("Text") { textAndTextComponents } label: { DefaultAssetLibrary.textLabel($0) } } } }
@ViewBuilder var shapesTab: some View { AssetLibraryTab("Shapes") { shapes } label: { DefaultAssetLibrary.shapesLabel($0) } }
@ViewBuilder var stickersTab: some View { AssetLibraryTab("Stickers") { stickers } label: { DefaultAssetLibrary.stickersLabel($0) } }
@ViewBuilder public var clipsTab: some View { AssetLibraryTab("Clips") { videosAndImages } label: { _ in EmptyView() } }
@ViewBuilder public var overlaysTab: some View { AssetLibraryTab("Overlays") { videosAndImages } label: { _ in EmptyView() } }
@ViewBuilder public var stickersAndShapesTab: some View { AssetLibraryTab("Stickers") { stickers shapes } label: { _ in EmptyView() } }
Asset Library body
View
Finally, multiple AssetLibraryTab
s are ready to be used in a SwiftUI TabView
environment. Use an AssetLibraryMoreTab
if you have more than five tabs to workaround various SwiftUI TabView
shortcomings. Editor solutions with a floating ”+” action button (FAB) show this AssetLibrary.body
View
.
var body: some View { TabView { AssetLibrarySceneModeReader { sceneMode in if sceneMode == .video { elementsTab uploadsTab videosTab audioTab AssetLibraryMoreTab { imagesTab textTab shapesTab stickersTab } } else { elementsTab imagesTab textTab shapesTab stickersTab } } }}
Asset Library Tab Views
In addition to its View.body
, the AssetLibrary
protocol requires to define elementsTab
, videosTab
, audioTab
, imagesTab
, textTab
, shapesTab
, and stickersTab
View
s. These are used when displaying isolated asset libraries just for the corresponding asset type, e.g., for replacing an asset.
@ViewBuilder var elementsTab: some View { AssetLibrarySceneModeReader { sceneMode in AssetLibraryTab("Elements") { elements(sceneMode) } label: { DefaultAssetLibrary.elementsLabel($0) } } }
@ViewBuilder var videosTab: some View { AssetLibraryTab("Videos") { videos } label: { DefaultAssetLibrary.videosLabel($0) } }
@ViewBuilder var audioTab: some View { AssetLibraryTab("Audio") { audio } label: { DefaultAssetLibrary.audioLabel($0) } }
@ViewBuilder var imagesTab: some View { AssetLibraryTab("Images") { images } label: { DefaultAssetLibrary.imagesLabel($0) } }
@ViewBuilder var textTab: some View { AssetLibrarySceneModeReader { sceneMode in if sceneMode == .video { AssetLibraryTabView("Text") { text.content } label: { DefaultAssetLibrary.textLabel($0) } } else { AssetLibraryTab("Text") { textAndTextComponents } label: { DefaultAssetLibrary.textLabel($0) } } } }
@ViewBuilder var shapesTab: some View { AssetLibraryTab("Shapes") { shapes } label: { DefaultAssetLibrary.shapesLabel($0) } }
@ViewBuilder var stickersTab: some View { AssetLibraryTab("Stickers") { stickers } label: { DefaultAssetLibrary.stickersLabel($0) } }
For the video editor solution, it is also required to define clipsTab
, overlaysTab
, and stickersAndShapesTab
View
s. These composed libraries are used as entry points instead of the FAB.
@ViewBuilder public var clipsTab: some View { AssetLibraryTab("Clips") { videosAndImages } label: { _ in EmptyView() } }
@ViewBuilder public var overlaysTab: some View { AssetLibraryTab("Overlays") { videosAndImages } label: { _ in EmptyView() } }
@ViewBuilder public var stickersAndShapesTab: some View { AssetLibraryTab("Stickers") { stickers shapes } label: { _ in EmptyView() } }