codeandtheory / ycoreui Goto Github PK
View Code? Open in Web Editor NEWCore components for iOS and tvOS to accelerate building user interfaces in code.
License: Apache License 2.0
Core components for iOS and tvOS to accelerate building user interfaces in code.
License: Apache License 2.0
Our Colorable
protocol helps users load (and unit test) colors from asset catalogs. It provides a fallback color to use in case the named color asset cannot be found. In that case though we should log a warning message.
Refer to the logging in ImageAsset.image
and SystemImage.image
, except that we should use YCoreUI.colorLogger
.
Colorable.color
if and only if returning the fallbackColor.YCoreUI.isLoggingEnabled == false
YCoreUI.colorLogger
calculateName()
method and cover it in unit tests (see ImageAsset.calculateName()
and associated unit tests for reference).calculateName()
There are a few things missing from our README
Protocols to aid loading string, color, and image assets
Localizable
, Colorable
, and ImageAsset
protocols with code examplesconstrainCenter
, constrainSize
, and constrainAspectRatio
methods.constrainEdges
and others, the code files for center, size, aspect ratio constraints should include code examples (can be the same ones added to the README).Most of the info in 2-4 has been added to the Y—CoreUI corporate Notion page.
Let's expand our Localizable protocol to include tableName support.
static var tableName: String?
to Localizable
tableName
to return nil
String.localized
to add table tableName: String? = nil
second parameter and pass that to the internal call to NSLocalizedString
.Localizable.localized
to pass Self.tableName
to String.localized(bundle:tableName:)
(Basically everywhere we're using bundle
in conjunction with Localizable
, we should also use tableName
.
String+LocalizedTests.swift
)Settings.strings
file under Tests/YCoreUITests/Assets/Strings/en.lproj
and add 3 string values to it. Declare a new SettingsConstants
enum which overrides tableName to find to the right strings file. Test that all the values in this enum load properly.It's a common pattern to have a string-based enum to hold the names of image assets in an asset catalog. In each case you need to add some sort of computed property or func to return the image loaded from the asset catalog. It would be nice to have a protocol that we could simply have our enum conform to and skip having to write that implementation again and again. (This is especially true for cases where you might wish to organize your images into multiple enums, which would require writing that image loading property/func once per enum.)
So let's declare a public protocol named ImageAsset
.
(It is patterned off the protocol Localizable
that is already part of YCoreUI. ImageAsset
will do for image assets what Localizable
does for string assets and Colorable
does for color assets).
ImageAsset
get
var bundle
of type Bundle
get
var namespace
of type String?
get
var fallbackImage
of type UIImage
loadImage
func that takes no parameters and returns UIImage?
get
var image
of type UIImage
bundle
defaults to Bundle.main
namespace
defaults to nil
fallbackImage
defaults to a custom-drawn image that will be obvious to visual QA is not correct. Perhaps a 16x16 image filled with UIColor.systemPink
loadImage
attempts to load the named image using (optional namespace
prepended to) rawValue
and bundle
(but returns nil
if the image does not exist in the asset catalog).image
uses loadImage
but nil-coalesces the result back to fallbackImage
so that it always returns something.ImageAsset
as described in Tasks aboveDesign specifies shadows differently than we do in code on iOS. They often refer to them as “elevations”. Let’s add a struct to encapsulate design shadows and apply them to views.
let
property offset: CGSize
let
property of blur: CGFloat
let
property of spread: CGFloat
let
property of color: UIColor
let
property of opacity: CGFloat
let
property of useShadowPath: Bool
(defaults to true)apply
that takes parameters layer: CALayer
and cornerRadius: CGFloat
that applies the elevation to a layer. This method should not set the shadowPath if useShadowPath == false
.pseudo code:
struct Elevation {
let offset: CGSize
let blur: CGFloat
let spread: CGFloat
let color: UIColor
let opacity: CGFloat
let useShadowPath: Bool // defaults to `true`
func apply(layer: CALayer, cornerRadius: CGFloat) {
layer.shadowColor = color.cgColor
layer.shadowOffset = offset
layer.shadowOpacity = opacity
layer.shadowRadius = blur / 2
guard useShadowPath else { return }
let rect = layer.bounds.insetBy(dx: -spread, dy: -spread)
layer.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
}
}
Because the implementation of spread
requires a shadow path, we will not be able to implement a shadow with spread for a view whose shape we do not know (e.g. useShadowPath == false
).
Elevation
as described in Tasks aboveAll of our constrain methods return either constraints or dictionaries of constraints but these are always discardable (they do not need to be retained because the constraint's parent view retains them).
constrainCenter
was supposed to be marked @discardableResult
(it was part of the acceptance criteria for the ticket), but it got overlooked.
mark constrainCenter
function return as @discardableResult
Our README should contain all necessary summary information about our framework. Currently we're missing usage information.
Copy the usage information from this Notion page (restricted) into the README. Add a new h2 section "Usage" and place that information within (using h3 and h4 subsections as appropriate). The "Usage" section should be between "Documentation" and "Installation". Do not copy over the section with the video links (because those are private).
tvOS uses many of the same UIKit components as iOS, so we should expand Y—CoreUI to support tvOS.
.tvOS(.v14)
platform to Swift package#if !os(tvOS)
or if os(iOS)
(keep future watchOS support in mind)We allow users to customize the appearance of our various components via an Appearance structure.
We should also allow them to customize the animation parameters (duration, delay, options, type of curve, etc). Would be nice if users could choose either spring damping animation or a regular animation curve and we could execute either one (even though they map to two different UIView
class methods).
Animation
struct, which should have the following properties:
UIView
with a new animate
class func that takes Animation
plus animations block and optional completion block as parameters. This method will call the appropriate animate
override and pass through the various parameters.We can use this in YSnackbar to fully customize the add, rearrange, and remove animations.
We have ImageAsset
protocol to allow users to load images from asset catalogs, unit test them, and have a fallback image (so that we can return a non-optional UIImage
. We should do the same thing for system images, e.g. for UIImage(systemName:)
optional initializer.
We could declare a new protocol SystemImage
and either derive it from ImageAsset
or just copy the needed functionality. While we would need:
static var fallbackImage: UIImage { get }
var image: UIImage { get }
func loadImage() -> UIImage?
we do not need (not applicable):
static var bundle: Bundle { get }
static var namespace: String? { get }
loadImage would have default implementation:
func loadImage() -> UIImage? { UIImage(systemName: rawValue) }
Our repo contains a config file that tells the Swift Package Index where to point for our documentation, but it has a typo in it and so does not work.
Fix the typo in .spi.yml
config file
current text:
version: 1
external_links:
documentation: "<https://yml-org.github.io/YCoreUI/">
new text:
version: 1
external_links:
documentation: "https://yml-org.github.io/YCoreUI/"
Create a new source file under Sources/YCoreUI/Extensions/UIKit
named UIView+constrainCenter.swift
Publicly extend UIView
to add the following two items:
Center
which is an OptionSet (type Uint
). It should have the following values:constrainCenter
takes the following parameters:_ center: Center = .all
to view2: Anchorable? = nil
offset: UIOffset = .zero
priority: UILayoutPriority = .required
isActive: Bool = true
constrain
anchor overrides (so .centerXAnchor
and .centerYAnchor
) and returns the one or two created constraints in a dictionary keyed by .centerX
and/or .centerY
, respectively. The result is discardable. When view2 == nil
pass superview
to constrain anchor (see otherView
in UIView+constrainEdges.swift
implementation). Generally refer to constrainEdges
implementation as it will be similar only you are creating up to two constraints instead of up to four.Tests/YCoreUITests/Extensions/UIKit
named UIView+constrainCenterTests.swift
UIViewContrainCenterTests
and mark it final
Let's expand the functionality of our system image loading protocol to include rendering mode.
(Theoretically we could also expand this to ImageAsset, but catalog assets can already specify rendering mode in the asset.)
public static var renderingMode: UIImage.RenderingMode? { get }
{ nil }
renderingMode
and if non-nil, modify the returned image with .withRenderingMode(:)
This will let users set the rendering mode on an entire group (enum) of images.
This will be useful in YStepper and YCalendarPicker.
Create a new source file under Sources/YCoreUI/Extensions/UIKit
named UIView+constrainAspectRatio.swift
Publicly extend UIView
to add the following method:
constrainAspectRatio
takes parameters_ ratio: CGFloat
offset: CGFloat = 0
relatedBy relation: NSLayoutConstraint.Relation = .equal
priority: UILayoutPriority = .required
isActive: Bool = true
ratio
as the multiplier and optional offset
as the constant.NSLayoutConstraint
but it is @discardableResult
Tests/YCoreUITests/Extensions/UIKit
named UIView+constrainAspectRatioTests.swift
UIViewContrainAspectRatioTests
and mark it final
UIView+constrainAnchor.swift
Use-case examples)Add the following two option rules (place them alphabetically under the opt-in rules) to .swiftlint.yml
config file:
implicit_return
missing_docs
Fix any SwiftLint violations that these rules generate.
It's a common pattern to have a string-based enum to hold the names of color assets in an asset catalog. In each case you need to add some sort of computed property or func to return the color loaded from the asset catalog. It would be nice to have a protocol that we could simply have our enum conform to and skip having to write that implementation again and again. (This is especially true for cases where you might wish to organize your colors into multiple enums, which would require writing that color loading property/func once per enum.)
So let's declare a public protocol named Colorable
.
(It is patterned off the protocol Localizable
that is already part of YCoreUI. Colorable
will do for color assets what Localizable
does for string assets).
Colorable
get
var bundle
of type Bundle
get
var namespace
of type String?
get
var fallbackColor
of type UIColor
loadColor
func that takes no parameters and returns UIColor?
get
var color
of type UIColor
bundle
defaults to Bundle.main
namespace
defaults to nil
fallbackColor
defaults to UIColor.systemPink
(we want something obvious to flag errors but also something unlikely to be actually used, so that we can unit tests that our colors load without fallback
)loadColor
attempts to load the named color using (optional namespace
prepended to) rawValue
and bundle
(but returns nil
if the color does not exist in the asset catalog).color
uses loadColor
but nil-coalesces the result back to fallbackColor
so that it always returns something.Colorable
as described in Tasks aboveCreate a new source file under Sources/YCoreUI/Extensions/UIKit
named UIView+constrainSize.swift
Publicly extend UIView
to add the following three methods:
constrainSize
takes parameters _ size: CGSize, relatedBy relation: NSLayoutConstraint.Relation = .equal, priority: UILayoutPriority = .required, isActive: Bool = true
and returns a dictionary [NSLayoutConstraint.Attribute: NSLayoutConstraint]
constrain
anchor overrides (so .widthAnchor
and .heightAnchor
).width
and .height
, respectively.constrainSize
takes parameters width: CGFloat, height: CGFloat, relatedBy relation: NSLayoutConstraint.Relation = .equal, priority: UILayoutPriority = .required, isActive: Bool = true
and returns same dictionary as 1.constrainSize
takes parameter _ dimension: CGFloat, relatedBy relation: NSLayoutConstraint.Relation = .equal, priority: UILayoutPriority = .required, isActive: Bool = true
and returns same dictionary as 1.Tests/YCoreUITests/Extensions/UIKit
named UIView+constrainSizeTests.swift
UIViewContrainSizeTests
and mark it final
The unused_import
rule is not actually being enforced because it runs during the analysis phase and not during the build phase.
(And I've tried running it as part of analysis and even then it doesn't work: if you satisfy the rule when analyzing the package, then imports are missing when trying to use the package.)
The current lint run warns about this rule not being run. We should just remove it.
Also the anyobject_protocol
warning is now enforced by Xcode itself and so no longer needs to be checked with SwiftLint. Again we should just remove it.
warning: 'unused_import' should be listed in the 'analyzer_rules' configuration section for more clarity as it is only run by 'swiftlint analyze'
The `anyobject_protocol` rule is now deprecated and will be completely removed in a future release.
unused_import
and anyobject_protocol
rules from .swiftlint.yml
config fileWhen we perform our color contrast checking, we only look at the RGB channels and not the A. When the foreground color has alpha, it becomes blended with the background color, and it is this blended color that we should perform the check against. (If the background color has alpha, then it becomes blended with whatever is behind it, which is unknown to our package).
.nan
for the contrast ratio and fail any comparisons.UIColor.secondaryLabel
when compared to UIColor.systemBackground
seems to meet AA contrast requirements across all color spaces (light/dark modes, normal/high contrast) but that's only because we're ignoring .secondaryLabel
's alpha channel. It is in fact transparent and the composite of it with .systemBackground
does not meet AA contrast requirements for normal text.
In our generated documentation, the top-level pages for extensions look very sparse. Like this:
By adding documentation comments to public extensions, we could improve the readability of both the source code and the generated documentation. The documentation shown above with extension comments added, becomes this:
Foundation/CGFloat+rounded.swift
Foundation/CGSize+inset.swift
Foundation/String+Localizable.swift
UIKit/UIScreen+scaleFactor.swift
UIKit/UITraitCollection+colorSpaces.swift
UIKit/UIView+constrainEdges.swift
(NSDirectionalRectEdge
extension only)public
declaration to the extension itself and remove it from the individual public
methods (any properties/methods declared as internal
/private
need to remain so) Do this only for the extensions you document in Step 1 aboveJazzy combines all extended properties / methods from multiple extensions into a single documentation page. e.g. for UIColor
we have 8 properties / methods spread across 5 separate extensions but they are all documented together on a single page. In this case Jazzy will not generate documentation for the extension (how would it know to combine the comments from the 5 extensions?). Documenting the extensions still has value though, but for now we will focus on classes that only have a single extension in our package. In a later ticket we can document the multiple extensions for UIColor
, UIView
, etc.
Add config file to point Swift Package Index to our GH page.
Add config file .spi.yml
to root
version: 1
external_links:
documentation: "https://yml-org.github.io/YCoreUI/"
Because we support extended RGB color space, individual channels can have values less than zero or greater than one. These do not format well for hexadecimal strings. For UIColor.rgbDisplayString
we should clamp the channel values to {0...1}
to avoid issues (and maybe append a
Add a UIColor initializer that supports the LCh color space.
This will require importing a couple of gists to support CieLAB colors but also creating a new "friendly" UIColor initializer to refer to the CieLAB version internally.
public func init(luminance: CGFloat, chroma: CGFloat, hue: CGFloat)
where luminance is 0.0 – 1.0
chroma is 0 – ~180
hue is in degrees not radians
In Issue #7 we expanded the Localizable
protocol with optional tableName
. This was intended to have a default implementation (returns nil
) but we forgot to mark it as public
, so effectively it has no default implementation.
mark Localizable.tableName
default implementation (in the extension) as public
.
Our SystemImage
protocol should load scalable system images by default. We do that by loading the image with UIImage.SymbolConfiguration(textStyle: aTextStyle)
. This allows the image scale according to the specified text style when Dynamic Type changes. And it will respond to Bold Text as well (to use heavier strokes).
static var textStyle: UIFont.TextStyle?
with default implementation = .body
static var configuration: UIImage.Configuration
with default implementation = UIImage.SymbolConfiguration(textStyle: textStyle)
when textStyle != nil
(need to see what configuration to use when it's nil. Maybe nil or maybe some default configuration that does nothing)Idea is the following:
.body
callout
or title
ultralight
or bold
) to adjust the appearance of the symbol. It's also an opportunity to apply hierarchical or palette information.Let's log to console from var image: UIImage
when loadImage()
returns nil
and we resort to the fallback image.
(This is preferable to logging from loadImage()
which can be expected to return nil
under certain unit tests.)
See how YMatterType logs when fonts don't load and let's adopt a similar pattern for declaring the Logger
to use.
This will be iOS 14+.
Let's leverage GitHub Actions to do some CI/CD work for us. It would be great to check SwiftLint + unit tests prior to allowing merges to main.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.