-
Notifications
You must be signed in to change notification settings - Fork 5
Case Study: Cloning Swift UI's Image view
Swift UI has a view called Image
, which roughly approximates Flutter's Image
widget. This case study is intended to help you clone other Swift UI views in this package by showing you the analysis and approach that was used to clone the Image
widget.
Let's begin with the most basic use of the Image
view. In it's simplest form, an Image
view displays a bitmap from the app's asset bundle, identified by a string name.
Image("logo")
Right off the bat we see a likely difference between a Flutter Image
and a Swift UI Image
. With a traditional Flutter image, the developer is likely to need to specify an asset directory path, has to specify the extension, and the developer needs to use the .asset()
named constructor:
Image.asset("assets/images/logo.png");
We'd like for Flutter developers to be able to retain the concision of Swift UI, for example:
Image("logo");
To achieve this concision, the swift_ui
Image
widget must do a few things:
- Offer a default constructor that takes a
String
as a required unnamed parameter. - Search the asset bundle for any file named "logo", regardless of extension
Additionally, to make it possible for the developer to avoid declaring a directory path, like "assets/images/", there needs to be some kind of tool to configure a search path for all images within a scope. Here's an example of what such a tool might look like:
SwiftUiImagePath(
"assets/images",
child: Any(
child: Number(
child: OfDescendants(
child: Image("logo"),
),
),
),
);
To let the user skip the image extension, we need to inspect all assets available in the asset bundle. Flutter doesn't appear to provide any direct way to query the assets, but here's a supposed workaround: https://stackoverflow.com/questions/68862225/flutter-how-to-get-all-files-from-assets-folder-in-one-list. In addition to that workaround for querying assets, the Image
widget needs to maintain a list of supported extensions. Furthermore, if multiple images exist with the same name, the Image
widget either needs to document its priority selection process, or throw an exception.
The Image
widget should make it possible to provide a desired bundle to search for the given image.
iOS includes various "system images". The Image
widget should support displaying a system image by name.
Image(systemName: "swift")
.resizable()
.scaledToFit()
.frame(width: 200, height: 200)
The Image
widget should include a constructor that takes a custom renderer.
By default, a Swift UI Image
interprets the image name, e.g., "logo", as a label that's localized. For example, if the image asset is called "pencil" and the app is running in a Spanish locale, the image will be given an accessibility label of "lápiz". The label can also be set explicitly with the label
property. In both cases, the value, which is localized, isn't known until run-time.
This feature conflicts with Flutter's current way of handling localization, which expects developers to request compile-time values, e.g., MaterialLocalizations.of(context).pencil
.
As a temporary solution, we might be able to read the .arb
file and index it ourselves.
There's a Flutter issue to provide key'ed localization at runtime: https://github.com/flutter/flutter/issues/105672
[init(size: CGSize, label: Text?, opaque: Bool, colorMode: ColorRenderingMode, renderer: (inout GraphicsContext) -> Void)](https://developer.apple.com/documentation/swiftui/image/init(size:label:opaque:colormode:renderer:))
Initializes an image of the given size, with contents provided by a custom rendering closure.
The Image
widget should implement the following APIs.
From an asset by name, labeled:
Image(
this.assetName, // name of image file, also "label" value if no label is provided
this.bundle, // (optional) bundle to find the image asset
this.label, // (optional) accessibility label
// -- following apply to most/all other configurations --
this.orientation, // (optional) image orientation (rotated, mirrored, flipped, etc)
this.resizable, // (optional) Sets the mode by which SwiftUI resizes an image to fit its space.
this.antialiased, // (optional) Specifies whether SwiftUI applies antialiasing when rendering the image.
this.renderingMode, // (optional) Whether to replace transparent pixels with foreground color
this.interpolation, // (optional) Specifies the current level of quality for rendering an image that requires interpolation.
this.allowedDynamicRange, // (optional) The allowed dynamic range for the view
);
From an asset by name, decorative (unlabeled):
Image.decorative(
this.assetName, // name of image file, also "label" value if no label is provided
this.bundle, // (optional) bundle to find the image asset
// -- add other common properties --
);
From a system image:
Image.system(
this.systemImageName, // name of the system image, i.e., SF Symbol, e.g., "trash.square.fill"
this.label, // (optional) accessibility label
this.variableValue, // (optional) system symbol customization value
// -- add other common properties --
);
From a dart:ui Image:
Image.fromImage(
this.memoryImage,
);
From a custom painter:
Image.fromRenderer(
this.renderer, // The custom painter that renders the image
this.size, // The size of the rendered image
this.label, // (optional) accessibility label
this.opaque, // A Boolean value that indicates whether the image is fully opaque.
// This may improve performance when true. Don’t render non-opaque
// pixels to an image declared as opaque. Defaults to false.
this.colorMode, // The working color space and storage format of the image. Defaults
// to ColorRenderingMode.nonLinear.
);