-
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
Stringas 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.
);