Post

Replies

Boosts

Views

Activity

Migrating @MainActor ViewModel to @Observable causing error
I get this error while migrating from ObservableObject to @Observable. Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context My original code: struct SomeView: View { @StateObject private var viewModel = ViewModel() } After migration: @MainActor @Observable class BaseViewModel { } @MainActor class ViewModel: BaseViewModel { } struct SomeView: View { @State private var viewModel = ViewModel() } As discussed here. It seems like @StateObject is adding @MainActor compliance to my View under the hood because it's wrappedValue and projectedValue properties are marked as @MainActor, while on @State they are not. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) @frozen @propertyWrapper public struct StateObject<ObjectType> : DynamicProperty where ObjectType : ObservableObject { ... @MainActor public var wrappedValue: ObjectType { get } .... @MainActor public var projectedValue: ObservedObject<ObjectType>.Wrapper { get } } One solution for this is to mark my View explicitly as @MainActor struct ViewModel: View but it have it side effects, for example code like: Button(action: resendButtonAction) { Text(resendButtonAttributedTitle()) } Will result a warning Converting function value of type '@MainActor () -> ()' to '() -> Void' loses global actor 'MainActor' While could be easily solved by using instead Button(action: { resendButtonAction() } ) { Text(resendButtonAttributedTitle()) } I still feel like marking the whole View explicitly as @MainActor is not a good practice. Adding fake @StateObject property to my view also do the trick, but it's a hack (the same for @FetchRequest). Can anyone think of a more robust solution for this?
1
1
1.6k
Jan ’24
SwiftUI ScrollView maintain position on new page load
Related to this post. In my chat view, each time I load new page (items are added from top), the ScrollView jumps to top instead of maintaining scrollPosition. Here is my scroll view: GeometryReader { geometryProxy in ScrollView(showsIndicators: false) { VStack(spacing: 0) { if viewModel.isLoading { LoadingFooter() } messagesView .frame(minHeight: geometryProxy.size.height - loadingFooterHeight - bottomContentMargins, alignment: .bottom) } } .scrollDismissesKeyboard(.interactively) .defaultScrollAnchor(.bottom) .scrollPosition(id: $scrolledId, anchor: .top) .contentMargins(.bottom, bottomContentMargins, for: .scrollContent) .onChange(of: scrolledId, scrollViewDidScroll) } And this is the messages view @ViewBuilder var messagesView: some View { LazyVStack(spacing: 0) { ForEach(sectionedMessages) { section in Section(header: sectionHeaderView(title: section.id)) { ForEach(section, id: \.id) { message in MessageView(message: message) .padding(.horizontal, .padding16) .padding(.bottom, .padding8) .id(message.id) } } } .scrollTargetLayout() } } Printing the scrolledId after a page load, I can see it hasn't changed, but the ScrollView position does.
0
2
1k
Oct ’23
URLSessionWebSocketTask custom close code
Is there a good way to access the web socket task actual close code? when looking at the task delegate: urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) I can see my custom code _closeCode = (long long) 4231 but the delegate (and the task's) closeCode variable is an enum (URLSessionWebSocketTask.CloseCode) so it have a fallback to the default invalid (0) for unknown values. I wonder if using webSocketTask.value(forKey: "_closeCode") Count as accessing private api, or if there is a cleaner way to access this raw value.
3
0
674
Oct ’23
scrollDismissesKeyboard interactively does not updates safe area size while in transition
Related to this StackOverflow post (not mine). In my chat view: ScrollView(showsIndicators: false) { messagesView } .safeAreaInset(edge: .bottom) { composerView } .scrollDismissesKeyboard(.interactively) Using interactively keyboard dismissing won't change the safe area size interactively causing a weird UI glitch like you can see in the post up above. The keyboard and composer play nice when I use it as tool bar, But I want my composer to always be visible (obviously), I've tried to play with FocusState to change the composer parent: .toolbar { ToolbarItem(placement: $isFocused ? .keyboard : .bottomBar) { bottomView } } But not only it redraws the view each time, it will also make the view lose its focus state, affectively releasing the keyboard. Plus it feels kind of a hack. What is the right way to make the composer move with the keyboard interactively and stay on screen while the keyboard is gone, like in iMessages?
0
1
684
Oct ’23
URLSessionDownloadDelegate never called
My request call site: let headers = coordinator.requestHeaders(path: headersPath) var request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: defaultTimeoutInterval) request.httpMethod = HTTPRequestMethod.get.rawValue request.allHTTPHeaderFields = headers let delegate = SessionDelegate(priority: priority, progressHandler: progressHandler) let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: .main) let (localUrl, response) = try await session.download(for: request) let data = try Data(contentsOf: localUrl) SessionDelegate: public class SessionDelegate: NSObject, URLSessionTaskDelegate, URLSessionDownloadDelegate { public typealias ProgressHandler = ((_ progress: Double) -> Void) public var metrics: URLSessionTaskMetrics? private let priority: TaskPriority private let progressHandler: ProgressHandler? //MARK: - Lifecycle init(priority: TaskPriority, progressHandler: ProgressHandler? = nil) { self.priority = priority self.progressHandler = progressHandler } //MARK: - URLSessionTaskDelegate public func urlSession(_ session: URLSession, didCreateTask task: URLSessionTask) { task.priority = priority.rawValue } public func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { self.metrics = metrics } public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { if let progressHandler { let progress = Progress(totalUnitCount: totalBytesExpectedToSend) progress.completedUnitCount = totalBytesSent progressHandler(progress.fractionCompleted) } } //MARK: - URLSessionDownloadDelegate public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { } public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { if let progressHandler { let progress = Progress(totalUnitCount: totalBytesExpectedToWrite) progress.completedUnitCount = totalBytesWritten progressHandler(progress.fractionCompleted) } } } For some reason I get delegate callbacks from URLSessionDelegate and URLSessionTaskDelegate but never from URLSessionDownloadDelegate. I've tried assigning the delegate to the task or the session (or both), but behavior remains the same. Not sure if related but when I try using background session configuration I crash on let (localUrl, response) = try await session.download(for: request) with the error Completion handler blocks are not supported in background sessions. Use a delegate instead. even though I use the async version of the download task, and assigning delegate to the session, it might point to the reason why none of the delegate functions is getting called (maybe?), but I have no clue why Xcode thinks I'm using completion handler.
4
1
1.4k
Sep ’23
MapCircle not updating on center or radius change
Using Xcode 15.0 beta 4 I have MapCoordinate struct: private struct MapCoordinate: Equatable { let location: Location let latitude: Double let longitude: Double var coordinate: CLLocationCoordinate2D { return CLLocationCoordinate2D(latitude: latitude, longitude: longitude) } init?(location: Location) { guard let latitude = location.latitude, let longitude = location.longitude else { return nil } self.location = location self.latitude = latitude self.longitude = longitude } } mapCoordinate and radius state variables: @State private var mapCoordinate: MapCoordinate? @State private var radius: CLLocationDistance And a mapView: @ViewBuilder var mapView: some View { Map { if let mapCoordinate { let annotationTitle = radius.toMeasurement(unit: .meters, convertedTo: .kilometers) Annotation(annotationTitle, coordinate: mapCoordinate.coordinate) { Image(systemImage: .mapPinEllipse) .fontWeight(.semibold) } MapCircle(center: mapCoordinate.coordinate, radius: radius) .foregroundStyle(.teal.opacity(circleOpacity)) .stroke(.teal, lineWidth: circleStrokeLineWidth) } } .mapStyle(.standard(elevation: .realistic)) .animation(.easeInOut, value: mapCoordinate) } When updating mapCoordinate the Annotation will animate correctly to the new coordinates, but the MapCircle will not update or change scale for neither mapCoordinate or radius changes. Ideas?
0
0
728
Jul ’23
Swift Charts BarMark bar shape when stacked
Hi I'm trying to apply clip shape on a BarMark with stacked values and the result is that it's applying the shape for each stack instead for the whole bar. What I want to achieve: What I got: I noticed that corner radius is applied to the whole bar but just the top of it: Is there any way to get this capsule shape when stacked? My code is: enum Value: Int, Hashable, CaseIterable {     case ftb     case nb     case remaining     var color: Color {         switch self {         case .ftb:             return .tint1         case .nb:             return .tint2         case .ramaining:             return .gray4         }     } } @ViewBuilder var barChart: some View {         Chart {             ForEach(dates, id: \.self) { date in                 let incomeSummary = incomeSummaries.first(where: { $0.date?.inSameDay(as: date) == true })                 ForEach(Value.allCases, id: \.self) { value in                     barMark(for: value, date: date, incomeSummary: incomeSummary)                         .foregroundStyle(value.color)                 } //                .cornerRadius(20, style: .continuous) //                .clipShape(Capsule())             }         }         .chartXAxis {             AxisMarks(values: dates) { date in                 AxisValueLabel(format: .dateTime.weekday(), centered: true)             }         }         .chartYAxis {             AxisMarks { mark in                 AxisValueLabel()             }         }     } private func barMark(for value: Value, date: Date, incomeSummary: IncomeSummary?) -> BarMark {         let amount = amount(for: value, in: incomeSummary)         return BarMark(x: .value("WeekDay", date, unit: .day,                                  calendar: Calendar.localTime),                 y: .value("Income", amount))     } Another issue, I've noticed that when AxisValueLabel is centered the last bar looses its label, is it a bug?
0
2
1.3k
Mar ’23
WKWebView doesn't run JavaScript when on background
I noticed that sometimes when the webview is moved to background (have another view controller on top), it stops JavaScript functions execution, and executes them only when returning back to foreground.I read about people having the same problems with socket.io, and most of the solutions was some kind of hack, like adding the webview to the key window, or looping dummy calls to evaluateJavaScript so the webview wouldn't go to idle mode and stop JavaScript.Those solutions work btw, but I don't like them at all.I was wondering if there was a better solution for this problem, any way to configure the webView to prevent going into idle mode, or more elegant way to prioritize JavaScript.Thanks.
2
0
3.8k
Nov ’18