Persist v1.2.2
Release Notes
Support animations when using Binding
provided via PersistStorage.projectedValue
Support animations when using Binding
provided via PersistStorage.projectedValue
Extending types in Swift support setting the scope for the extension, i.e. public
, internal
, or private
, with internal
being implicit if nothing is specified.
This may seem useful, but given the following snippet it's impossible to know what the scope of a function is:
func doSomething() { // Do the thing }
Increase deployment targets to fix archive builds using Xcode 13
Action will no longer push to existing branch when there are no changes
Swift Packages are JSON files that describe a collection of packages. This post will explain how to sign these packages with a trusted certificate entirely from the terminal. These methods should work on Linux and macOS alike. At the end I describe how to have Swift on Linux implicitly trust these packages.
Using this technique I have published my own package collection.
If you're targeting macOS only and find GUIs more intuitive I recommend following the “Swift Package Collection” blog post from Shaps, which is the post that finally made this “click” for me.
Thread-safety has been improved, including:
InMemoryStorage
Here's a small PR for a bug that I think has just gone unnoticed because we've never used the sectionInset
property of the UICollectionViewFlowLayout
, and we've never used the contentInset
property of UICollectionView
.
The issues looks to be that the layout's sectionInset
property is applied on a per-section basis, but the collection view's contentInset
was not being honoured.
It was also calculated using insetBy(dx:dy:)
, which will modify the width by -dx * 2
, which would double the expected insets.
Hotfix for changes to Google image searches. Clicking links will no longer open the AMP popover and the AMP popover should be removed from the bottom of the screen
Initial release
Support for Date
s in UserDefaultsStorage
This PR is to add support for running on watchOS. Since Xcode 12.5 watchOS has been supported but Nimble doesn't currently compile for watchOS.
A new target has been added to the project, along with explicit support in the Package.swift.
I don't believe anything here is a breaking change.
I have a fork of Quick that also supports watchOS. I'll create a PR for that if this PR gets approved and merged.
At WWDC21 Apple introduced DocC, a tool for creating archives of Swift documentation that includes the static files required to host a version of the documentation on a website.
In this post I will summarise various methods of serving a DocC archive:
All the examples provided here are hosting the DocC archive for VaporDocC, the Vapor middleware I wrote for hosting DocC archives.
The package(s) being submitted are:
I have either:
swift ./validate.swift
.Or, checked that:
Package.swift
file in the root folder.swift package dump-package
with the latest Swift toolchain.https
) and the .git
extension.With WWDC 2021 just around the corner I've been thinking about what I'd like to see there.
A lot of the popular discourse around this time of year is focussed on features of the operating systems but I want to look at what I'd like to see as a developer for Apple platforms.
I love to develop for Apple platforms but it can often be a painful process. May is like a christmas for Apple developers.
Partial fix for #31
Added to work around https://bugs.swift.org/browse/SR-14103
Overall this is pretty minor but when dealing with a lot of SingleElementSections
that are infrequently updated is does start to slow down.
I thought a faster approach would be to use generics on replace(element:)
to only check for nil
when the value is Optional
, but generics don't allow for this kind of overload.
Also added some basic tests. The performance tests show a ~9% performance increase.
Replacing https://github.com/composed-swift/ComposedUI/pull/15
This PR is being used to diff between the OpenNet fork.
Hopefully other PRs will be merged before this (e.g. https://github.com/composed-swift/Composed/pull/21), which should lead to this PR having no changes and purely to ensure some of the history is not lost.
We've discussed returning -1
not being great previously, but now we have the 2.0
branch we can make this change :)
@bill201207 found that UINib(nibName:bundle:)
is a big part of the poor performance when our app launches.
Ultimately batching updates will help with a lot of things like this, but really that'll just be masking some of the performance issues so it's useful to find issues like this now.
When displaying an alert in SwiftUI, if the value used to calculate whether the alert is presented is both Optional
and does not conform to Identifiable
1 it is often recommended to use a separate flag, similar to:
struct ContentView: View { @State private var alertText: String? @State private var isPresentingAlert = false var body: some View { Button("Show Alert") { self.alertText = "Alert Text" self.isPresentingAlert = true } .alert(isPresented: $isPresentingAlert) { Alert(title: Text(alertText!)) } } }
There are 2 main downsides to this:
alertText
is not set back to nil
, which may cause bugs and will increase memory usage (even if only a little in this case)isPresentingAlert
flag needs to be managedTo work around these issues I create a small extension to Binding
the allows this same code to be updated to:
struct ContentView: View { @State private var alertText: String? var body: some View { Button("Show Alert") { self.alertText = "Alert Text" } .alert(isPresented: $alertText.mappedToBool()) { Alert(title: Text(alertText!)) } } }
The extension is fairly small and simple:
import os.log import SwiftUI extension Binding where Value == Bool { /// Creates a binding by mapping an optional value to a `Bool` that is /// `true` when the value is non-`nil` and `false` when the value is `nil`. /// /// When the value of the produced binding is set to `false` the value /// of `bindingToOptional`'s `wrappedValue` is set to `nil`. /// /// Setting the value of the produce binding to `true` does nothing and /// will log an error. /// /// - parameter bindingToOptional: A `Binding` to an optional value, used to calculate the `wrappedValue`. public init<Wrapped>(mappedTo bindingToOptional: Binding<Wrapped?>) { self.init( get: { bindingToOptional.wrappedValue != nil }, set: { newValue in if !newValue { bindingToOptional.wrappedValue = nil } else { os_log( .error, "Optional binding mapped to optional has been set to `true`, which will have no effect. Current value: %@", String(describing: bindingToOptional.wrappedValue) ) } } ) } } extension Binding { /// Returns a binding by mapping this binding's value to a `Bool` that is /// `true` when the value is non-`nil` and `false` when the value is `nil`. /// /// When the value of the produced binding is set to `false` this binding's value /// is set to `nil`. public func mappedToBool<Wrapped>() -> Binding<Bool> where Value == Wrapped? { return Binding<Bool>(mappedTo: self) } }
The extension isn't tied directly to showing an alert or a sheet and can be used in any context, but this is one of the better examples of its usage.
This extension is available on GitHub under the MIT license.
1 If it does conform to Identifiable
use alert(item:content:)
This adds the ComposedLayout
and ComposedUI
packages, which will close #16.
The base for this is 2.0-beta
, which we can use for testing new changes, which can also break API.
If you go to the repo settings you can (temporarily) set the default branch to 2.0-beta
and we can add a note to the README stating this is the beta release and the stable release is available in the master
branch.
Once merged and these changes are made the READMEs for ComposedLayout
and ComposedUI
repos can be updated to point to this repo and their repos archived.
This is a new type of section that is similar to ComposedSectionProvider
, but rather than flattening each of the children in to a single SectionProvider
it flattens them in to a single Section
.
The ComposedUI
side of this has been updated to support multiple cells per section, with a convenience for FlatSection
that delegates the calls to each of the flattened sections.
This is a breaking change since the protocol requirements have changed. I have set the base of the PR to merge-all-libraries
since that's also a breaking change and this PR relies on those changes.
edit: We've moved to our own fork to allow for more rapid development. The latest changes are in https://github.com/opennetltd/Composed/tree/feature/FlatSection, which will eventually be merged back in to this repo once some of the other PRs/issues have been resolved.
In my open source frameworks I usually use the last major Xcode (currently 11.7), latest (currently 12.3) and latest beta (soon to be 12.4), but this project has only been setup for the latest version of Xcode.
If we feel it's good to run the tests across multiple Xcode versions I have created https://github.com/JosephDuffy/update-xcode-version-action, which we can use to keep the versions in-sync with the versions available as part of GitHub actions.
For now I've removed all use a explicit Xcode versions.
Regression introduce in 1.1.1.
Would occur when a segmented provider was the last child of a composed provider and the selected segment changed.
This could have also been fixed in SegmentedSectionProvider
but changing the _currentIndex
after the delegate has been notified (which is closer to what ComposedSectionProvider
does) but we have nothing in the API contract stating this must be done, so really it's an issue with the caching in ComposedSectionProvider
.
These properties are accessed quite frequently and can be cached with a little extra processing.
This is another finding from screens that have a large number of composed sections.
This and https://github.com/composed-swift/ComposedUI/pull/15 seem to be the main areas of performance issues, although that doesn't mean that once these are working and merged we won't uncover more 😅
Thanks to @bill201207 for their contributions to this change.
The idea of this is that without breaking the API the collection view will batch updates.
Inserting a large number of sections is the main area of performance loss we are currently encountering, because the sections are inserted individually and not batched. This change alone has reduce the initial load time of one our screens (which has 100-150 sections added at once) from 30-45 seconds down to less than a second (at least it is not noticeable).
I had created https://github.com/composed-swift/Composed/pull/17 to try and address this, which has the advantage that it would apply to other view types (e.g. UITableView
), but I believe does not offer the same performance improvements and it is restricted to a single ComposedSectionProvider
.
This is a draft to collect feedback; as you can see there are some TODOs but I think there's enough implemented to provide an overview of the changes that would be required to implement this.
This does not currently work; there are situations that cause the collection NSInternalInconsistencyException', reason: 'Invalid update
error. I have some failing tests that demonstrate what the result should be.
When adding a lot of sections (we have a screen which inserts 100~175 on initial load) the performance is quite poor.
Along with the improve-composed-section-provider-performance
branch (which I want to fully validate before making a PR) performing these changes in batch helps a lot.
Add Persister
/Persisted
extensions on watchOS
builder(for:)
function to aid with building complex type (thanks to @randomeizer)As mentioned in https://github.com/composed-swift/ComposedUI/issues/13 and https://github.com/composed-swift/ComposedUI/issues/8 there are some scenarios where the collection view’s data is out-of-sync with the data in composed.
As mentioned in https://github.com/composed-swift/ComposedUI/issues/13 calling layoutIfNeeded
can trigger the data to be in sync again. In this I have added it to mappingWillBeginUpdating(_:)
which appears to solve the problem.
It might be needed in replace(sectionProvider:)
(because reloadData
is called) and/or mappingDidInvalidate(_:)
(for the same reason) but I’m still investigating.
I have validated this fix against https://github.com/composed-swift/ComposedUI/issues/8 and it fixes the crash.
https://github.com/composed-swift/ComposedUI/issues/13 still needs to be investigated and may require layoutIfNeeded
to be called in mappingDidInvalidate
. Marking as a draft until this is checked.
The Xcode 12 beta includes Swift 5.3 but drops support for iOS 8.x. This means that Swift packages that support iOS 8 will cause a warning:
The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99.
It's not possible to remove this warning within a project that depends on a Swift package with a deployment target of iOS 8, but it is possible to fix this in the dependency without removing support for iOS 8 for older versions of Swift. There are multiple way this can be accomplished.
This is a major API change so is in a v2 branch. If we agree on a direction we could start adding PRs in to this branch.
This change would enable multiple changes to be applied at once, e.g. by requiring balanced calls to mappingWillBeginUpdating
and mappingDidEndUpdating
and only performing the updates when an equal number of mappingDidEndUpdating
calls have been made. This could be a small performance improvement but would also improve animations.
By adding the performUpdate
closure the changes to the model layer can be applied only when the consumer is expecting them, e.g. for UICollectionView
this would be inside performBatchUpdates
and would fix the existing crashes that occur when performing a change when there are pending layout changes.
Some tests and types have been commented out since this is more of a starting point for a discussion around the API than it is a solid implementation.
This is the closest I can see the API being (although it's still a breaking change) but maybe a bigger change would be better.
The biggest PITA is that the sections need to keep track of "current" data (e.g. what's being displayed by a collection view) and the "pending" data (e.g. what's about to be applied). I don't think this adds any memory overhead but it does add much more to the knowledge required when implementing a section.
This needs more tests to ensure full coverage, but also to prove that this was previously broken and is now fixed.
The added function is public because I'm using it to extract a subset of the entries in the archive that contains symlinks.
#185, possibly #62.
These were quite hard to track down since the errors that would occur would often be visual and not a crash, although in some scenarios it did seem to trigger a crash.
I think this highlights the need for a much larger test suite.
It also made me question the insert(_:at:)
functions because it's not really clear what the index
is. Should it insert it at the index in the context of the sections and providers (as it does now), or just sections?
For example, with:
If I insert a section at index 3 should it look like:
or
The tests are currently essentially empty but at a minimum this makes sure things compile on Xcode 11.7 and 12.0.
@shaps80 I think this will need updates to the repo settings to enable GitHub Actions.
Introduced in #9. #12 adds GitHub actions to ensure this doesn't occur again.
Previously there was no way to get the index of a child or even know if a Section
or SectionProvider
is a child of a ComposedSectionProvider
.
On top of these functions new insert(_:after:)
and insert(_:before:)
have been added for convenience.
The completion
block is called when the animation has completed, but at the end of the updates
block the data has already been updated (from the perspective of the UICollectionView
).
Not calling reset()
and setting defersUpdate = false
could cause crashes e.g. due to changes being applied multiple times.
The best way to recreate this is to have a series of updates occur very close to each other but with the "Slow Animation" setting enabled in the simulator.
This change might also apply to table views, I have not checked.
The crash would often be along the lines of "Tried to delete section at index 11 but there were only 11 sections" or would occur in elementsProvider(for:)
with the error "No UI configuration available for section 11"
when performing a non-data change e.g. a reload of a section
Improve API for Subscription/Cancellable
Add documentation
I am currently maintaining numerous Swift Packages that don't receive a constant flow of updates, but do receive updates when new Swift updates come out, or as I think of useful additions.
To ensure that I can make some of these less frequent updates without too much friction and with confidence in their correctness I rely heavily on GitHub Actions, which I'll go over in this blog post.
Much better handling of optional values, which also allows for non-optional values
Release candidate 1 for 1.0 stable release
Has lots of tests but little documentation. API has changes a lot since initial 0.1.0 but I’m happy with it now. Hopefully it is consistent and (once docs are added) easy to use.
A common pattern when using closures in Swift is to add [weak self]
in the captures list to hold a weak reference to self
and avoid a retain cycle. This is then often followed by the following:
guard let self = self else { return }
But I often forget that capture lists can capture other variables in the current scope, so I thought I'd highlight some other use cases.
Today I have released the 1.0.0 version of a Swift package that aids with adding Equatable
and Hashable
conformance by using KeyPath
s.
The package is available on GitHub.
I created the Swift Playground that sparked this concept in December 2018, so this concept has been rattling around in my brain for a couple of years. The API has changed a lot since the original concept, but the core has stayed the same: a protocol that requires a single function to be implemented that uses KeyPath
s to synthesise Equatable
and/or Hashable
conformance.
Today marks 1 year since I released a blog post demonstrating an implementation of Partial in Swift, and it also marks the release of the 1.0.0 version of a Swift package for Partial.
The package is available on GitHub and supports SwiftPM, Carthage, and CocoaPods.
This blog post will go over some of the changes that have been made since the original blog post, my rationale when making certain decisions, and how I have thought about maintenance. If you want to try out Partial and see how it can be used head over to the GitHub page.
Structs are incredibly useful in Swift, especially when representing static read-only data. However, the values of a struct often come from multiple sources, such as view controllers, network requests, and files on disk, which can make the creation of these structs cumbersome.
There are numerous methods to work around this, but each have their downsides. One of these methods is to change the struct to a class and update the properties to vars, but this removes the advantages of read-only structs. Another is to make a "builder" object, but the API of this object must be kept in-sync with the object is wraps.
Partial
eliminates these problems by providing a type-safe API for building structs by utilising generics and KeyPath
s. Although I learned of the concept of Partial
through TypeScript – which [provides Partial
as a built-in type][1] – the Swift implementation supports many more use cases.