oscbyspro / diffabletextviews Goto Github PK
View Code? Open in Web Editor NEWAs-you-type formatting in SwiftUI
License: Apache License 2.0
As-you-type formatting in SwiftUI
License: Apache License 2.0
This is because the current implementation assumes that all formatting characters are unique, but in this case the localized currency expression contains a fraction separator.
func testVirtualCharactersAreNotAlwaysUnique() {
let number = -1234567.89
let currencyCode = "PAB"
let locale = Locale(identifier: "rhg-Rohg_MM")
let formatted = number.formatted(.currency(code: currencyCode).locale(locale))
XCTAssertEqual(formatted, "-B/. 1,234,567.89") // currency contains fraction separator
}
A small demo app that makes it easy to preview and test each style's behavior.
Fixed by: 751ad87.
The current structure cannot export text styles, as it would create cyclical dependencies. It is also ill suited for supporting additional platforms such as macOS, because DiffableTextViews becomes too crowded. A solution is to restructure the project as
Level 0:
- DiffableTextKit
Level 1:
- NumericTextStyles
- PatternTextStyles
- DiffableTextViews_iOS
- DiffableTextViews_macOS
Level 2:
- DiffableTextViews [exports]
where the dependencies are Level 0 —> Level 1 —> Level 2
and the current DiffableTextViews target is split into two new targets: DiffableTextKit (models) and DiffableTextViews_iOS. A new DiffableTextViews then exports based on platform.
The .swiftpm/xcode folder is a pain to maintain. It contains schemes and benchmarks, so I think it can be removed. https://github.com/apple/swift-package-manager has added .swiftpm to .gitignore, for reference.
Type could be remade as:
NumericTextStyle<Double>
NumericTextStyle<Double>.Currency
NumericTextStyle<Double>.Percent
so that it looks similar to:
FloatingPointFormatStyle<Double>
FloatingPointFormatStyle<Double>.Currency
FloatingPointFormatStyle<Double>.Percent
While splitting the project into multiple modules keeps things organized
import DiffableTextViews
does effectively nothing. Instead, one must import it alongside a style
import DiffableTextViews
import NumericTextStyles
import PatternTextStyles
I think it would be worthwhile investigating the use of @_exported import
.
Can be done after v1.0.0.
It should be possible, and many things seem to indicate that it works better.
Update 1: Actually, could probably use Value.init from String methods. Value to String is bad, but there should not be a problem converting Number to Value, since Number is ASCII-able by design. Have to think about it.
Update 2: Forgot, value.init will break the percent style due to asymmetry. That said, changing the locale to en_US or in another way using ASCII should work, and probably works better than using locales nobody knows about.
I think it would be a neat addition, and it would not be too complicated to add. That said, I thought about it and decided against it. I’m unpaid and unemployed, and I have no need for it myself. Can’t eat kilometers.
Is it possible? It'd be much nicer than the current XCTSkip/If/Unless solution.
If max value is “999” and the text is “99.”, changing the text to “999.” should remove the fraction separator. Instead, input is cancelled because “999” is the max value and cannot fit fraction digits.
A decent idle format: [partially filled pattern] + | + [remainders cut off by the first mismatch].
It can be reduced to two methods (plus localization)—one for upstream and one for downstream. All other requirements can be remade as style specific implementation details. I'll fix this first, then see how that affects the other issues.
I'm not sure this is solvable on my end. Decimal and Double seem OK.
func testFloat16() {
let value = 1.23 as Float16
let locale = Locale(identifier: "en_US")
let result = FloatingPointFormatStyle<Float16>.Percent(locale: locale).format(value)
XCTAssertEqual(result, "123.046875%")
}
func testFloat32() {
let value = 1.23 as Float32
let locale = Locale(identifier: "en_US")
let result = FloatingPointFormatStyle<Float32>.Percent(locale: locale).format(value)
XCTAssertEqual(result, "123.000002%")
}
Note to future self.
On the topic of: https://www.swift.org/blog/utf8-string/
I’ve been playing around with converting wrapped UInt8 to String and the following seems to be the most performant by far (of what I have been able to come up with). This only works if each Glyph is a struct, however, because enums are zero-based and therefore mistranslated by unsafeBitCast(glyphs, to: [UInt8].self).
func testMakeStringUsingUnsafeBitCast() {
measure {
var accumulator: [UInt8] = []
// capacity can be calculated O(1)
accumulator.reserveCapacity(glyphs.count * 3)
for _ in 0 ..< 3 {
// appends 1_000_000 UTF8 characters
accumulator.append(contentsOf: unsafeBitCast(glyphs, to: [UInt8].self))
}
let _ = String(bytes: accumulator, encoding: .utf8)!
}
}
It would improve the performance of the number.characters() call in Lexicon.swift.
Pattern: anchor is not set if an invalid character is entered when the following conditions are all true:
Label is broken, but because all cases where it fails are covered by other parts of the process it is not noticed. It's kinda funny, but at the same time it is bad and should be fixed.
Consider disabling fraction separator if there are no fraction digits and the value has reached its upper bound.
Apple's numeric FormatStyle(s) expose(s) locale/currencyCode. It'd make sense to mimic this behavior.
Note to future self.
SwiftUI.TextField can add a toolbar above the keyboard as shown below.
view.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") { /* action */ }
}
}
This does not work on DiffableTextField, however. It should be fixed, if possible, otherwise an alternative should be offered.
Some still crash. Should not be too difficult, since most is done, I think.
Cached schemes/lexicons are never found and a new one is created each update...
The current public facing methods get the job, but they are not uniform in their design.
proxy.key(return: .done)
proxy.keyboard(.alphabet)
As an example, the above methods look like they were designed with different ideas in mind.
proxy.color(tint: .blue)
And the above method changes the selection color. It's bad.
Priority: low.
NumericTextStyles & PatternTextStyles are messy. DiffableTextViews is kinda nice, tho.
for currency in currencies { for locale in locales { /* style.interpret */ } } // works
for locale in locales { for currency in currencies { /* style.interpret */ } } // crash
In response to changes upstream, fraction digits that exceed the maximum fraction digits length may round away from zero. This rarely matters, but sometimes the behavior may be unexpected. Assume
so that when the user interacts with the view (editing changed counts as a change upstream) the following happens
When the view is editing and the locale is set to "bg" Bulgarian and the currency is set to USD, the application crashes. This is because the currency code "щ.д." is interpreted as two dots, which is invalid, then the result is force unwrapped.
Thoughts 1: This is isolated to the “value -> number -> value” section in the commit(value:) method. One solution is therefore to always use the plain number format style in that section. No formatting asymmetry would be introduced with this solution.
Thoughts 2: It is not quite so simple, actually, because doing this results not in a crash but in a state where changes are invalid—also because of those two dots.
It should be restricted to system fonts. Monospacing can then use /withDesign(_:).
The locale/currency pair BG/USD should work as intended.
Maybe: use a strictly localized bidirectional map, then add a another map to localize ASCII inputs.
TODO: check if it is a valid assumption that localized currency codes (and symbols) always use characters that are disjoint from the set of characters used to format numbers. For example, none of the characters in "щ.д." (as formatted by BG/USD) are used in the number formatted by the same style.
In SwiftUI.TextField text alignment is set via .multilineTextAlignment(_:) and it is only applied on setup.
Note to future self.
Default fraction limits should depend on the currency used. It should match values provided by Foundation.NumberFormatter. Below is an example of desired behavior,
NumericTextStyle<Decimal>.Currency(code: "USD") ==
NumericTextStyle<Decimal>.Currency(code: "USD").precision(integer: 1..., fraction: 2...2)
The UITextField says it has correct offset but the caret somehow appears at the start. For example, while testing, offset == 6 is at the end of the UITextField, and offset == 5 is somehow at the start…
It is needed for another feature I’m thinking about. The current solution works well, but is a bit too hack-y. The new implementation should still search directionally, as far as needed, and be skipped when not needed.
There are three parameters that affect the outcome when synchronizing the view:
The contents of the synchronize method should not be called unless one or more has changed.
Learned that UIViewRepresentableContext provides environment values. It can/should replace the use of environment observers. https://developer.apple.com/documentation/swiftui/uiviewrepresentablecontext
Lots of files. Good for project in hyper-dev mode. Can consolidate now.
let value = 1.23 as Float16
let inaccurate = value.formatted(.number) // "1.230469"
let value = -1.23 as Float32
let inaccurate = value.formatted(.number.precision(.fractionLength(0...))) // "1.2300000190734863"
Mimicking environment values adds a lot of overhead, for library authors and especially for users. Nobody wants 12 different modules with 12 different ways of writing the same value to the environment. In an alternative universe, where environment values are exposed, there is peace and harmony and all nations sing Kumbayah because custom 3rd party iOS libraries just work
as if Apple themselves had built them.
Requires public SwiftUI.Font to UIKit.UIFont conversion.
Requires exposure of whatever is used behind the scenes.
Private
environment values == decent
3rd party libraries.
Public
environment values == amazing
3rd party libraries.
Note to future self. Make sure input space is intuitive.
This already happens when: bounds < 0, because the value is clamped. But it would be nice if it also were the case when the upper bound equals zero (and lower bounds is less than zero), since there is no positive input space.
I haven't tested it and I'm not sure what might need to change, but auto-fill compatibility would be nice to have.
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.