Issue #948
iOS 17 has a new Stand by mode so SwiftUI introduces containerBackground for the system to decide when to draw background. It also automatically applies margin to widget so we may need to disable that
To update existing widgets, we can write some useful extension
extension View { @ViewBuilder func safeContainerBackground(@ViewBuilder content: () -> some View) -> some View { if #available(iOS 17.0, *) { self.containerBackground(for: .widget, content: content) } else { self....
Issue #938
To let app and extension to talk to the same database, we need to use AppGroup. Here is how to use replacePersistentStore
Replaces one persistent store with another
actor DatabaseMigrator { @AppStorage("DatabaseMigrator.hasMigrated") var hasMigrated = false func migrateIfNeeded() { guard !hasMigrated else { return } migrate() hasMigrated = true } private func migrate() { let oldContainer = NSPersistentCloudKitContainer(name: "Bookmarks") guard let oldStoreUrl = oldContainer.persistentStoreDescriptions.first?.url, let newStoreUrl = Constants....
Issue #937
First, you need to enable iCloud Documents capability. Go to target settings -> Signing & Capabilities -> iCloud ` Then inside your Info.plist, add this with your iCloud identifier and app name
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.com.onmyway133.PastePal</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerName</key>
<string>PastePal</string>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
</dict>
</dict>
To access to your iCloud Drive folder, we use FileManager to retrieve the folder.
Returns the URL for the iCloud container associated with the specified identifier and establishes access to that container....
Issue #934
There are a few keychain wrappers around but for simple needs, you can write it yourself
Here is a basic implementation. I use actor to go with async/await, and a struct KeychainError to contain status code in case we want to deal with error cases.
accessGroup is to define kSecAttrAccessGroup to share keychain across your apps
public actor Keychain { public struct KeychainError: Error { let status: OSStatus } let service: String let accessGroup: String?...
Issue #932
Add Share extension and Action extension respectively in Xcode. We can use the same code to both extension
SwiftUI I usually make a ShareView in SwiftUI with ShareViewModel to control the logic
struct ShareView: View { @ObservedObject var vm: ShareViewModel var body: some View { NavigationStack(path: $vm.routes) { List {} } } } In ShareViewController, we can just conform to UIViewController and add our SwiftUI view as child view controller...
Issue #931
AppStore screenshots Screenshot specifications
iPhone 6.7" Portrait 1290 x 2796
iPhone 6.5" Portrait 1242 x 2688
In-App Purchase screenshots In-app purchase information
iOS 640 x 920 tvO 1920 x1080 pixels macOS 1280 x 800 pixels
Issue #930
AppIntents Declare AppShortcutsProvider, note that appShortcuts uses @AppShortcutsBuilder syntax
import AppIntents struct OurShortcutsProvider: AppShortcutsProvider { static var shortcutTileColor: ShortcutTileColor = .lightBlue @AppShortcutsBuilder static var appShortcuts: [AppShortcut] { AppShortcut(intent: AskIntent(), phrases: [ "Hey Joy", "Ask \(.applicationName)" ]) } } We can create an app intent in code
import AppIntents import OpenAI struct AskIntent: AppIntent { static var title: LocalizedStringResource = "Hey Joy" static var description: IntentDescription = "Ask me anything" @Parameter(title: "Prompt") var prompt: String?...
Issue #922
EnvironmentValues Views in SwiftUI can react to configuration information that they read from the environment using an Environment property wrapper
Updated for iOS 17
Issue #921
Use HStack with TextField and a little extension
extension Binding where Value == Int { var toString: Binding<String> { Binding<String>( get: { "\(wrappedValue)" }, set: { wrappedValue = Int($0) ?? 0 } ) } } struct TextFieldStepper: View { @Binding var value: Int var body: some View { HStack(spacing: 0) { TextField("", text: $value.toString) .textFieldStyle(.roundedBorder) .frame(width: 50) Stepper("", value: $value) } } }
Issue #920
For iOS 16 and macOS 13.0
TextEditor(text: $text) .scrollContentBackground(.hidden) For below, use [SwiftUI-Introspect](https://github.com/siteline/SwiftUI-Introspect)
TextEditor(text: $text) .instrospectTextView { $0.drawsBackground = false }
Issue #919
WWDC23 introduces lots of new additions to SwiftUI, notably Metal shader support with these modifiers
colorEffect: Returns a new view that applies shader to self as a filter effect on the color of each pixel. layerEffect: Returns a new view that applies shader to self as a filter on the raster layer created from self. distortionEffect: Returns a new view that applies shader to self as a geometric distortion effect on the location of each pixel....
Issue #918
Interesting SwiftUI Q&A during WWDC23
Observable vs ObservableObject Q: With the new SwiftUI @Observable macro, are there any cases where ObservableObject would still be a better alternative?
A: Use ObservableObject when you need to back deploy to before iOS 17
A: SwiftUI registers dependencies is a value is read during the evaluation of body. Indirect modifications should invalidate the dependencies.
containerRelativeFrame in ScrollView Q: For the containerRelativeFrame, does that work cleanly in a List as well as a ScrollView?...
Issue #916
WWDC23 brings new additions to SwiftUI
Scrolling The scroll transition modifier is very similar to the visual effect modifier Curt used earlier for the welcome screen. It lets you apply effects to items in your scroll view.
I鈥檓 using the new containerRelativeFrame modifier to size these park cards relative to the visible size of the horizontal scroll view.
I鈥檇 like my park cards to snap into place....
Issue #915
Here are my favorite iOS articles
Why Your App Looks Better in Sketch Managing objects using Locks and Keys in Swift Proof in Functions When Not to Use an Enum
Issue #911
Make an parallelTask function that wraps TaskGroup
public func parallelTask(@ParallelTaskBuilder builder: () -> [ParallelTaskBuilder.Work]) async { await withTaskGroup(of: Void.self) { group in for work in builder() { group.addTask { await work.value } } } } @resultBuilder public struct ParallelTaskBuilder { public typealias Work = Task<Void, Never> public static func buildExpression(_ expression: Work?) -> [Work] { if let expression = expression { return [expression] } return [] } public static func buildExpression(_ expression: Work) -> [Work] { [expression] } public static func buildExpression(_ expression: [Work]) -> [Work] { expression } public static func buildBlock(_ components: Work....
Issue #910
Use one-sided range operator let string = "Hello world" string[string.startIndex...] // Hello world string[..<string.endIndex] // Hello world Substring let string = "Hello world" let range = string.startIndex ..< string.index(string.startIndex, offsetBy: 5) string[range] // Hello Convert to and from NSRange let string = "Hello world" let range = string.startIndex... let nsRange = NSRange(range, in: string) let regex = NSRegularExpression(pattern: pattern) let matches = regex.matches(in: string, range: nsRange) for match in matches { let range = Range(match....
Issue #908
When we add another UIWindow, then its rootViewController will decide the style of the status bar, not the rootViewController of the keyWindow anymore
childForStatusBarStyle The usual way to fix this is to defer the decision to the correct rootViewController, like in our HUDViewController
class HUDViewController: UIViewController { override var childForStatusBarStyle: UIViewController? { let windows = view.window?.windowScene?.windows ?? [] for window in windows where window != self.view.window { return window....
Issue #906
Read more https://developer.apple.com/documentation/widgetkit/making-a-configurable-widget What is the purpose of getSnapshot method from WidgetKit
Issue #905
Protect mutable state with Swift actors
Actor reentrancy
Imagine we have two different concurrent tasks trying to fetch the same image at the same time. The first sees that there is no cache entry, proceeds to start downloading the image from the server, and then gets suspended because the download will take a while. While the first task is downloading the image, a new image might be deployed to the server under the same URL....
Issue #904
Consider this code where we have an ObservableObject with fetch1 and async fetch2, and a fetch inside ContentView
Here the observation in Xcode 14
ViewModel.fetch1: run on main thread ViewModel.fetch2: run on cooperative thread pool ContentView.fetch: run on main thread import SwiftUI import CoreData import Combine class ViewModel: ObservableObject { @Published var string = "" func fetch1() { let url = URL(string: "https://google.com")! let data = try!...