I have a view which sends its size to a child, so the child can update its size relative to its parent. (The child needs to be able to respond to the parents size, and also update its own size from a drag event.)
In the example below, the child sets its width to 40% of the parent in it's init. When the window is first shown, the child updates its width correctly. When you resize the window, however, the initializer is called and the size is recalculated, but the child never updates its width. The state seems to never update.
When the code is called outside of the initializer (such as pressing a button) then the state updates correctly.
Why doesn't the state update in the init after its called more than once? How would I accomplish this?
struct ContentView: View {
var body: some View {
GeometryReader { geo in
ZStack {
MyView(containerSize: geo.size)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.border(Color.green, width: 1)
}
}
}
struct MyView: View {
let containerSize: CGSize
@State var realSize: CGSize
init(containerSize: CGSize) {
self.containerSize = containerSize
let newSize = CGSize(width: containerSize.width * 0.4, height: containerSize.height)
print("INIT", newSize)
_realSize = State(initialValue: newSize)
}
func updateWidth() {
realSize = CGSize(width: containerSize.width * 0.4, height: containerSize.height)
}
var body: some View {
ZStack {
Button(action: {
self.updateWidth()
}) {
Text("Update Width")
}
}
.frame(width: realSize.width, height: realSize.height)
.background(Color.blue)
}
}
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
In my app, I've been using ModelActors in SwiftData, and using actors with a custom executor in Core Data to create concurrency safe services.
I have multiple actor services that relate to different data model components or features, each that have their own internally managed state (DocumentService, ImportService, etc).
The problem I've ran into, is that I need to be able to use multiple of these services within another service, and those services need to share the same context. Swift 6 doesn't allow passing contexts across actors.
The specific problem I have is that I need a master service that makes multiple unrelated changes without saving them to the main context until approved by the user.
I've tried to find a solution in SwiftData and Core Data, but both have the same problem which is contexts are not sendable. Read the comments in the code to see the issue:
/// This actor does multiple things without saving, until committed in SwiftData.
@ModelActor
actor DatabaseHelper {
func commitChange() throws {
try modelContext.save()
}
func makeChanges() async throws {
// Do unrelated expensive tasks on the child context...
// Next, use our item service
let service = ItemService(modelContainer: SwiftDataStack.shared.container)
let id = try await service.expensiveBackgroundTask(saveChanges: false)
// Now that we've used the service, we need to access something the service created.
// However, because the service created its own context and it was never saved, we can't access it.
let itemFromService = context.fetch(id) // fails
// We need to be able to access changes made from the service within this service,
/// so instead I tried to create the service by passing the current service context, however that results in:
// ERROR: Sending 'self.modelContext' risks causing data races
let serviceFromContext = ItemService(context: modelContext)
// Swift Data doesn't let you create child contexts, so the same context must be used in order to change data without saving.
}
}
@ModelActor
actor ItemService {
init(context: ModelContext) {
modelContainer = SwiftDataStack.shared.container
modelExecutor = DefaultSerialModelExecutor(modelContext: context)
}
func expensiveBackgroundTask(saveChanges: Bool = true) async throws -> PersistentIdentifier? {
// Do something expensive...
return nil
}
}
Core Data has the same problem:
/// This actor does multiple things without saving, until committed in Core Data.
actor CoreDataHelper {
let parentContext: NSManagedObjectContext
let context: NSManagedObjectContext
/// In Core Data, I can create a child context from a background context.
/// This lets you modify the context and save it without updating the main context.
init(progress: Progress = Progress()) {
parentContext = CoreDataStack.shared.newBackgroundContext()
let childContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
childContext.parent = parentContext
self.context = childContext
}
/// To commit changes, save the parent context pushing them to the main context.
func commitChange() async throws {
// ERROR: Sending 'self.parentContext' risks causing data races
try await parentContext.perform {
try self.parentContext.save()
}
}
func makeChanges() async throws {
// Do unrelated expensive tasks on the child context...
// As with the Swift Data example, I am unable to create a service that uses the current actors context from here.
// ERROR: Sending 'self.context' risks causing data races
let service = ItemService(context: self.context)
}
}
Am I going about this wrong, or is there a solution to fix these errors?
Some services are very large and have their own internal state. So it would be very difficult to merge all of them into a single service. I also am using Core Data, and SwiftData extensively so I need a solution for both.
I seem to have trapped myself into a corner trying to make everything concurrency save, so any help would be appreciated!
I’m using the new StoreKit 2 APIs in my production app that sells a non-consumable iap. I’ve tested restoring purchases with a StorekitConfiguration in development, along with my own personal Apple ID in production and it works.
The problem is that I’m getting lots of users reporting that restore purchase does’t work on a consistent basis. So my question is, is there something in my production code that is wrong or broken? Or is this a common problem with end users themselves and not my app? My app used the old version of store kit previously, so could that cause this issue?
I always recommend they make sure they’re signed in with the same Apple ID they purchased the app with. Sometimes recommending the user to quit and relaunch the app, or restart there computer fixes the issue. I’ve gotten multiple reports that the only fix was completely deleting then reinstalling the app. And there’s a good portion which non of the above work.
Here’s the code that handles restoring purchases:
/// Attempts to restore any purchases by the user.
/// This is called by the restore purchase button
public func restorePurchase() {
Task {
try? await AppStore.sync()
await updateActiveProducts()
}
}
/// Updates the status of which products have been purchased/are an active subscription
@MainActor public func updateActiveProducts() async {
var purchasedIds: [String] = []
// Iterate through all of the user's purchased products.
for await result in Transaction.currentEntitlements {
do {
let transaction = try checkVerified(result)
// Has the App Store revoked this transaction?
// If it is upgraded do nothing, there is an active transaction for a higher level of service
if transaction.revocationDate != nil || transaction.isUpgraded {
continue
}
// Check if the subscription is expired
if let expirationDate = transaction.expirationDate, expirationDate < Date() {
continue
}
if transaction.productType == .nonConsumable || transaction.productType == .autoRenewable {
purchasedIds.append(transaction.productID)
}
} catch {
// Transaction not verified, don't do anything
}
}
// This is a published property which unlocks the features of the app
self.purchasedIdentifiers = purchasedIds
}
Any advice would be appreciated!
I’m trying to create a property wrapper that that can manage shared state across any context, which can get notified if changes happen from somewhere else.
I'm using mutex, and getting and setting values works great. However, I can't find a way to create an observer pattern that the property wrappers can use.
The problem is that I can’t trigger a notification from a different thread/context, and have that notification get called on the correct thread of the parent object that the property wrapper is used within.
I would like the property wrapper to work from anywhere: a SwiftUI view, an actor, or from a class that is created in the background. The notification preferably would get called synchronously if triggered from the same thread or actor, or otherwise asynchronously. I don’t have to worry about race conditions from the notification because the state only needs to reach eventuall consistency.
Here's the simplified pseudo code of what I'm trying to accomplish:
// A single source of truth storage container.
final class MemoryShared<Value>: Sendable {
let state = Mutex<Value>(0)
func withLock(_ action: (inout Value) -> Void) {
state.withLock(action)
notifyObservers()
}
func get() -> Value
func notifyObservers()
func addObserver()
}
// Some shared state used across the app
static let globalCount = MemoryShared<Int>(0)
// A property wrapper to access the shared state and receive changes
@propertyWrapper
struct SharedState<Value> {
public var wrappedValue: T {
get { state.get() }
nonmutating set { // Can't set directly }
}
var publisher: Publisher {}
init(state: MemoryShared) {
// ...
}
}
// I'd like to use it in multiple places:
@Observable
class MyObservable {
@SharedState(globalCount)
var count: Int
}
actor MyBackgroundActor {
@SharedState(globalCount)
var count: Int
}
@MainActor
struct MyView: View {
@SharedState(globalCount)
var count: Int
}
What I’ve Tried
All of the examples below are using the property wrapper within a @MainActor class. However the same issue happens no matter what context I use the wrapper in: The notification callback is never called on the context the property wrapper was created with.
I’ve tried using @isolated(any) to capture the context of the wrapper and save it to be called within the state in with unchecked sendable, which doesn’t work:
final class MemoryShared<Value: Sendable>: Sendable {
// Stores the callback for later.
public func subscribe(callback: @escaping @isolated(any) (Value) -> Void) -> Subscription
}
@propertyWrapper
struct SharedState<Value> {
init(state: MemoryShared<Value>) {
MainActor.assertIsolated() // Works!
state.subscribe {
MainActor.assertIsolated() // Fails
self.publisher.send()
}
}
}
I’ve tried capturing the isolation within a task with AsyncStream. This actually compiles with no sendable issues, but still fails:
@propertyWrapper
struct SharedState<Value> {
init(isolation: isolated (any Actor)? = #isolation, state: MemoryShared<Value>) {
let (taskStream, continuation) = AsyncStream<Value>.makeStream()
// The shared state sends new values to the continuation.
subscription = state.subscribe(continuation: continuation)
MainActor.assertIsolated() // Works!
let task = Task {
_ = isolation
for await value in taskStream {
_ = isolation
MainActor.assertIsolated() // Fails
}
}
}
}
I’ve tried using multiple combine subjects and publishers:
final class MemoryShared<Value: Sendable>: Sendable {
let subject: PassthroughSubject<T, Never> // ...
var publisher: Publisher {} // ...
}
@propertyWrapper
final class SharedState<Value> {
var localSubject: Subject
init(state: MemoryShared<Value>) {
MainActor.assertIsolated() // Works!
handle = localSubject.sink {
MainActor.assertIsolated() // Fails
}
stateHandle = state.publisher.subscribe(localSubject)
}
}
I’ve also tried:
Using NotificationCenter
Making the property wrapper a class
Using NSKeyValueObserving
Using a box class that is stored within the wrapper.
Using @_inheritActorContext.
All of these don’t work, because the event is never called from the thread the property wrapper resides in.
Is it possible at all to create an observation system that notifies the observer from the same context as where the observer was created?
Any help would be greatly appreciated!
To avoid using file URLs, I'm using the WKURLSchemeHandler to create a custom url to load my assets into a webview.
This works great, except that certain ways of loading assets from the custom URL results in the error:
Failed to load resource: Origin null is not allowed by Access-Control-Allow-Origin. Loading an asset in an image tag, css background property, or using a javascript import works. But using css mask, or javascript fetch does not.
You can see the problem by right-clicking in the window, and clicking show developer tools.
Css masks are used widely throughout the content in my webview, and I'm also using fetch. So I need both of these to work.
What is causing this error? Why does it only happen when using certain methods of loading resources?
import SwiftUI
import WebKit
struct ContentView: View {
var body: some View {
HTMLView().frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct HTMLView: NSViewRepresentable {
func makeNSView(context: Context) -> WKWebView {
/* Create the custom url handler and webview */
let config = WKWebViewConfiguration()
config.setURLSchemeHandler(CustomURLHandler(), forURLScheme: "asset")
let webview = WKWebView(frame: .zero, configuration: config)
/* Enable developer tools so we can right click and show the inspector */
webview.configuration.preferences.setValue(true, forKey: "developerExtrasEnabled")
/* Example html that does and doesn't work: */
let html = """
<style>
#box {
width: 100px;
height: 100px;
border: 1px solid green;
/* This works: */
background: url(asset://test.svg);
}
#box2 {
width: 100px;
height: 100px;
border: 1px solid red;
background: blue;
/* This does not work: */
-webkit-mask-image: url(asset://test.svg);
}
</style>
<p>This box loads the asset using the background property:</p>
<div id="box"></div>
<p>This box results in the error, and doesn't show the image with the css mask:</p>
<div id="box2"></div>
<script>
// Using javascript fetch also doesn't work:
fetch('asset://test.svg')
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
</script>
"""
webview.loadHTMLString(html, baseURL: nil)
return webview
}
func updateNSView(_ nsView: WKWebView, context: Context) {}
}
class CustomURLHandler: NSObject, WKURLSchemeHandler {
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
guard let url = urlSchemeTask.request.url else {
return
}
/* Handle the custom url assets here. In this example I'm just returning a test svg: */
let testImage = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"><svg width=\"100%\" height=\"100%\" viewBox=\"0 0 19 18\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xml:space=\"preserve\" xmlns:serif=\"http://www.serif.com/\" style=\"fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;\"><g transform=\"matrix(1,0,0,1,-540,-709.618)\"><g transform=\"matrix(0.5,0,0,0.666667,467,536)\"><g transform=\"matrix(2,0,0,1.5,-934,-804)\"><path d=\"M549,712.6C550.895,709 554.684,709 556.579,710.8C558.474,712.6 558.474,716.2 556.579,719.8C555.253,722.5 551.842,725.2 549,727C546.158,725.2 542.747,722.5 541.421,719.8C539.526,716.2 539.526,712.6 541.421,710.8C543.316,709 547.105,709 549,712.6Z\" style=\"fill:rgb(255,0,0);\"/></g></g></g></svg>"
guard let data = testImage.data(using: .utf8) else { return }
let urlResponse = URLResponse(url: url, mimeType: "image/svg+xml", expectedContentLength: data.count, textEncodingName: nil)
urlSchemeTask.didReceive(urlResponse)
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {}
}
I have a WKWebView that contains a js text editor, built on top of a content editable div. Currently, inline predictions on Mac break the text editors functionality significantly every time there's a prediction. There's not a way for me to fix this on the js side unless I can know when a prediction is shown.
I've tried disabling inline predictions and writing tools with the web view config, but it doesn't work:
let config = WKWebViewConfiguration()
config.writingToolsBehavior = .none
config.allowsInlinePredictions = false
let webView = WKWebView(frame: .zero, configuration: config)
I've also tried disabling all spellcheck and autocorrect features in the html but that doesn't work either:
<div contenteditable="true" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off"></div>
Is there anything I can do to turn it off? Or, is it possible to know when the WebView is predicting text?
I have a custom input view in my app which is .focusable(). It behaves similar to a TextField, where it must be focused in order to be used.
This works fine on all platforms including iPad, except when when an external keyboard is connected (magic keyboard), in which case it can't be focused anymore and becomes unusable.
Is there a solution to this, or a workaround? My view is very complex, so simple solutions like replacing it with a native view isn't possible, and I must be able to pragmatically force it to focus.
Here's a very basic example replicating my issue. Non of the functionality works when a keyboard is connected:
struct FocusableTestView: View {
@FocusState private var isRectFocused: Bool
var body: some View {
VStack {
// This text field should focus the custom input when pressing return:
TextField("Enter text", text: .constant(""))
.textFieldStyle(.roundedBorder)
.onSubmit {
isRectFocused = true
}
.onKeyPress(.return) {
isRectFocused = true
return .handled
}
// This custom "input" should focus itself when tapped:
Rectangle()
.fill(isRectFocused ? Color.accentColor : Color.gray.opacity(0.3))
.frame(width: 100, height: 100)
.overlay(
Text(isRectFocused ? "Focused" : "Tap me")
)
.focusable(true, interactions: .edit)
.focused($isRectFocused)
.onTapGesture {
isRectFocused = true
print("Focused rectangle")
}
// The focus should be able to be controlled externally:
Button("Toggle Focus") {
isRectFocused.toggle()
}
.buttonStyle(.bordered)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
}
}
I have wrapped an NSOutlineView with NSViewRepresentable which works great. The problem is that when an item is expanded, the outline view doesn't change height and the animation doesn't work properly (see gif below). When I put the outline view in an NSScrollView, it works correctly, but I need to be able to put multiple outline views within a single SwiftUI scrollview.
struct Test: View {
var body: some View {
ScrollView {
SwiftUiOutline()
// Ultimately I need multiple outline views here
}
}
}
struct SwiftUiOutline: NSViewRepresentable {
func makeNSView(context: Context) -> NSOutlineView {
let view = NSOutlineView()
// Outline view is set up here, and connected to the coordinator delegate
return view
}
class Coordinator: CustomOutlineViewController {
// The coordinator handles all the outline view delegation, etc.
}
// ...
}
The problem:
How it should work:
I'm trying to create a wrapper around a WKWebView for SwiftUI, and I'm not sure how to prevent a memory leak.
I've created an ObservableObject which handles controlling the WebView, and a custom view that displays it:
public class WebViewStore: ObservableObject {
var webView: WKWebView = WKWebView()
// List of event handlers that will be called from the WebView
var eventHandlers: [String: () -> Void] = [:]
deinit {
// This is never called once an action has been set with the view,
// and the content view is destroyed
print("deinit")
}
// All the WebView code, including custom JavaScript message handlers, custom scheme handlers, etc...
func reloadWebView() { }
}
The web view needs to be able to communicate with JavaScript, so I've added an onAction() method which gets called when the WebView gets a javascript event.
View wrapper:
struct WebView: NSViewRepresentable {
let store: WebViewStore
func makeNSView(context: Context) -> WKWebView {
return store.webView
}
func updateNSView(_ view: WKWebView, context: Context) {}
}
extension WebView {
/// Action event called from the WebView
public func onAction(name: String, action: @escaping () -> Void) -> WebView {
self.store.eventHandlers[name] = action
return self
}
}
Usage that creates the retain cycle:
struct ContentView: View {
@StateObject var store = WebViewStore()
var body: some View {
VStack {
// Example of interacting with the WebView
Button("Reload") {
store.reloadWebView()
}
WebView(store: store)
// This action closure captures the WebViewStore, causing the retain cycle.
.onAction(name: "javascriptMessage") {
print("Event!")
}
}
}
}
Is there a way to prevent the retain cycle from happening, or is there a different SwiftUI pattern that can to handle this use case? I can't use [weak self] because the view is a struct. I need to be able to receive events from the WebView and vice versa.
I'm trying to create a note-taking like app that uses NSPersistentCloudKitContainer and core data.
The store uses the NSMergeByPropertyObjectTrumpMergePolicy, which is fine for almost every property. For example, if the name of a file is changed on two different devices, then it's fine to use the latest value.
The problem is that the note text cannot be overridden by the latest value if it's changed on two devices at once. It needs to be detected as a conflict so the user can choose which version they want to keep.
I can replicate the behavior by turning off wifi on one device and writing content, then writing content on a different device at the same time. When I turn the wifi back on, whichever device saved the changes last completely overrides the other device's text.
What I'd like to accomplish is detect when there is a conflict of text, then create a duplicate file called "Conflicted Copy." Bonus points if someone can tell me how Apple Notes magically merges text without ever creating a conflict. I really only need a simple solution though that prevents data loss.
Any help in the right direction would be appreciated!
I'm trying to invite an Apple ID as a search ads api user, however when I try to accept the invitation from the invited Apple ID, I get the error "Unable to create Search Ads Basic account".
When I try to login directly to search ads using that account, I get the message "We're sorry. This Apple ID is no longer authorized for Apple Search Ads."
I've also tried this with a separate Apple ID account, and the same problems happened.
I've followed the steps in this article: https://searchads.apple.com/help/campaigns/0022-use-the-campaign-management-api
Is there something I'm missing? Do I need to create a brand new Apple ID for this to work?