make `SFSymbol` struct
ddddxxx opened this issue · comments
I believe SFSymbol
is naturally a String
backed struct, not enum.
public struct SFSymbol: RawRepresentable {
public let rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}
}
When new symbols were added (like #53), user can do it on their own:
public extension SFSymbol {
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
static let sealFill = SFSymbol(rawValue: "seal.fill")
}
We can also break the massive SFSymbol.swift
file into smaller fils (v1, v2, v2.1, deprecated).
I believe all existing functions won't be compromised. I can work on a pr with your permission.
As I mention in #66, initializing from rawValue doesn't currently work. This change would solve that issue in allowing you to initialize with any string as a rawValue, but it would hurt the "safety" of the library by always returning a SFSymbol
with any raw value, even if it is not a valid symbol.
The current enum has a massive allCases array with every case, so I guess the same thing could be done here with every static instance. When initializing from a raw value, check if it's valid in the allCases array?
@ddddxxx Thanks for your idea and your offer to help!
I only thought about this quickly, but it seems like this is really a much better approach compared to the enum
that is currently used:
- It would fix #66. As @isvvc mentioned, maybe it would be good to adjust the public raw value initializer so that it checks for the existence of the symbol. Of course that would mean that users wouldn't be able to add their own symbols as an extension to the
SFSymbol
type as you suggested, but the whole point of theSFSymbol
type is that it's safe and if anyone could retrieve aSFSymbol
by extending the type, it would have the same unsafe character as when using aString
directly for the initializer. What do you think? 🤔 Also, I guess an optional raw value initializer isn't possible, so we would need afatalError
in case of non-existence of the providedrawValue
in our known strings. To increase performance, we should still use a private raw value initializer for the built-in symbols which circumvents the existence check. - It would stop our
SFSymbol
file from getting larger and larger... - Maybe, it's possible to summarize multiple symbols under one
@availability
check, also reducing code size & complexity:
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
extension SFSymbol {
static let sealFill = SFSymbol(rawValue: "seal.fill")
static let sealFill2 = SFSymbol(rawValue: "seal.fill2")
...
}
(Of course, for deprecations, a per-symbol-@availability
statement would still be needed.)
Unfortunately, I currently don't have the time to tackle this large refactoring, but it would be great, if you could help @ddddxxx! If you change something, please make sure that for new versions of SF Symbols the generator project can be used to generate all relevant files. If you have questions about that project (which is quite hacky), feel free to ask! I also recommend that you first test the desired design on a smaller scale by manually coding it before spending much time on the generator project.
I realized the file size is a real issue. The excessively large SFSymbol.swift
file freeze Xcode and my git gui app 🤦♂️.
I realized the file size is a real issue. The excessively large
SFSymbol.swift
file freeze Xcode and my git gui app 🤦♂️.
On my machine, it still works somehow, but further growing the file would definitely break it, too...
but the whole point of the SFSymbol type is that it's safe
I don't think so. SFSymbol doesn't tell you if a symbol name is safe. It tells you if a symbol name is known. A failable initializer will give user false impression that they can validate future symbols, which they can not.
and if anyone could retrieve a SFSymbol by extending the type, it would have the same unsafe character as when using a String directly for the initializer.
I don't think users will use Image(systemSymbol: .init(rawValue: "???"))
. They should use predefined symbols. and if one extend the type themself, he takes full responsibility.
I still prefer a transparent wrapper, like NSAttributedString.Key
. So users won't expect more than what we provide. Validate symbol name dynamically is impossible. They can check known symbol name with SFSymbol.allCases.contains(.mySymbol)
.
@ddddxxx Sorry for my late reply!
What you said, makes sense to me – a symbol accessible via the SFSymbol
type is known, and the one who made it known takes the responsibility that it's safe.
I like the idea of turning SFSymbol
into a struct, especially because is allows splitting SFSymbol.swift into multiple files, which in turn allows the user to add their own, custom SFSymbol
cases.
Regarding rawValue
and initializing via rawValue
: Apart from user-extensions to SFSymbol
, there is no need for an init(rawValue:)
. Why would anyone want to create an SFSymbol
by its internal string representation? Avoiding this string-based initialization is the whole point of SFSafeSymbols.
Again, I don't think there should be a public init(rawValue:)
. Even more, I don't believe rawValue
is the right name for the property. I'd suggest something like systemSymbolName
, as it communicates the purpose of the property much clearer than rawValue
.
The question remains how users can create their own SFSymbol
instances. There has to be a public initializer to make this possible, but we can give it a name which makes explicit that it should only be used with custom, user-defined SFSymbols.
public struct SFSymbol {
public let systemSymbolName: String
internal init(systemSymbolName: String) {
self.systemSymbolName = systemSymbolName
}
/// Create an SFSymbol from your custom, user-defined symbol.
public init(customName: String) {
self.systemSymbolName = customName
}
}
Removing the rawValue
property and the expectation that lies upon the relation between rawValue
and init(rawValue:)
would resemble the actual nature of SFSymbol
more closely (and would immediately fix #66), and this specific naming (systemSymbolName
) is what I would suggest instead.
@knothed Thanks for your ideas, I agree with you. Such a naming & initializer scheme would indeed solve multiple issues and better fit the new modeling as a struct
(in contrast to the previous enum
implementation where a rawValue
is quite common) 👍
@Stevenmagdy As you are working on this over at #72, maybe you also want to comment and / or possibly implement this approach in your PR?
I don't believe there is a need for Codable
, but if wanted, Codable
could be easily implemented without the conformance to RawRepresentable
.
In fact, we explicitly do not want SFSymbol
to conform to RawRepresentable
as this would suggest some relationship between a rawValue
and an init(rawValue:)
, which is in fact not there.
I agree that we should give up the RawRepresentable
because of the initializer, but I think we should keep the rawValue
property since it's direct and doesn't suggest if the symbol is custom or not (vs. systemSymbolName
), and for source compatibility. @knothed @fredpi
public struct SFSymbol: Equatable, Hashable {
public let rawValue: String
internal init(systemName: String) {
self.rawValue = systemName
}
public init(customName: String) {
self.rawValue = customName
}
}
@Stevenmagdy @knothed @ddddxxx Yes, the rawValue
name is probably better, because
[it] doesn't suggest if the symbol is custom or not
Regarding Codable
: If it was possible for people to use the Codable
conformance with the previous version (which I'm not sure about, that should be checked), it would be best to add this manually for the new version (should be straightforward as there's only the rawValue
property to encode / decode). If it wasn't possible with the previous version, I'm indifferent on whether we should add it.