Post

Replies

Boosts

Views

Activity

Reply to Observation and MainActor
Now that @Observable macro has been out a little bit, what's the latest thinking on this? 🤔 How would one apply @MainActor to the ViewModel of the following code? Currently, doing so gives the "Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context" error on the @State line in the TestApp struct. import SwiftUI @main struct TestApp: App { @State private var vm = ViewModel() var body: some Scene { WindowGroup { ContentView() .environment(vm) } } } @Observable @MainActor class ViewModel { var showDetails: Bool = false } struct ContentView: View { @Environment(ViewModel.self) var vm var body: some View { @Bindable var vm = vm VStack { DetailsButton(showDetails: $vm.showDetails) if vm.showDetails { Text("This is my message!") } } } } struct DetailsButton: View { @Binding var showDetails: Bool var body: some View { Button("\(showDetails ? "Hide" : "Show") Details") { showDetails.toggle() } } }
Nov ’23
Reply to How to avoid Swift 6 concurrency warning from UIAccessibility.post()
Thanks for the quick reply and good to know it's not me! (it usually is 😆) I'm importing SwiftData and SwiftUI, so I tried @preconcurrency import SwiftUI and it does suppress the warning. But I'm new at this and I'm not sure if that's OK or if I should be importing UIKit instead? Code seems to work so I'm guessing there's a lot of overlap, but I'd like to avoid any unintended side effects down the road.
Jun ’24
Reply to How to build a top/bottom split view with a dynamically-sized divider?
I finally figured it out—this code achieves the behavior I was looking for: import SwiftUI enum SnapPoint: CGFloat, CaseIterable { case top = 0.0 case partial = 0.33 case bottom = 1.0 var value: CGFloat { return self.rawValue } static let allValues: [CGFloat] = SnapPoint.allCases.map { $0.rawValue } } struct ContentView: View { @State private var totalHeight: CGFloat = 0 @State private var topHeight: CGFloat = 0 @State private var dividerHeight: CGFloat = 0 @State private var showMore: Bool = true @State private var currentSnapPoint: SnapPoint = .partial @State private var previousDividerHeight: CGFloat = 0 let snapPoints: [SnapPoint] = SnapPoint.allCases var body: some View { GeometryReader { geometry in VStack(spacing: 0) { TopView() .frame(maxWidth: .infinity) .frame(height: max(0, topHeight)) .background(Color.red.opacity(0.3)) .border(.pink) .clipped() DividerView(showMore: $showMore) .zIndex(1) .background( GeometryReader { dividerGeometry in Color.clear .onAppear { dividerHeight = dividerGeometry.size.height if totalHeight == 0 { totalHeight = geometry.size.height topHeight = calculate(.partial) } previousDividerHeight = dividerHeight } .onChange(of: dividerGeometry.size.height) { withAnimation(.snappy(duration: 0.2)) { let deltaHeight = dividerGeometry.size.height - previousDividerHeight previousDividerHeight = dividerGeometry.size.height dividerHeight = dividerGeometry.size.height if currentSnapPoint != .top { topHeight = max(0, topHeight - deltaHeight) } if totalHeight == 0 { totalHeight = geometry.size.height topHeight = (totalHeight - dividerHeight) / 2 } } } } ) .gesture( DragGesture() .onChanged { value in topHeight = calculateDraggedTopHeight(value.translation.height) } .onEnded { _ in withAnimation(.snappy(duration: 0.2)) { let (snapPoint, height) = nearestSnapPoint(for: topHeight) topHeight = height currentSnapPoint = snapPoint } } ) BottomView() .frame(maxWidth: .infinity) .frame(height: max(0, geometry.size.height - topHeight - dividerHeight)) .background(Color.green.opacity(0.3)) .border(.pink) .clipped() } .onChange(of: geometry.size.height) { totalHeight = geometry.size.height topHeight = min(topHeight, totalHeight - dividerHeight) } } } func calculateDraggedTopHeight(_ translation: CGFloat) -> CGFloat { return max(0, min(topHeight + translation, totalHeight - dividerHeight)) } func nearestSnapPoint(for height: CGFloat) -> (SnapPoint, CGFloat) { let calculatedPoints = snapPoints.map { ($0, calculate($0)) } let nearest = calculatedPoints.min(by: { abs($0.1 - height) < abs($1.1 - height) }) ?? (.partial, height) return nearest } func calculate(_ point: SnapPoint) -> CGFloat { switch point { case .top: return 0 case .partial: return (totalHeight * point.value) - dividerHeight case .bottom: return totalHeight - dividerHeight } } } struct DividerView: View { @Binding var showMore: Bool var body: some View { VStack(spacing: 0) { Text(showMore ? "Tap to hide 'More'" : "Tap to show 'More'") .padding(16) .multilineTextAlignment(.center) if showMore { Text("More") .padding(16) } } .frame(maxWidth: .infinity) .background(Color(.systemBackground)) .onTapGesture { withAnimation(.snappy(duration: 0.2)) { showMore.toggle() } } } } struct TopView: View { var body: some View { Text("Top") } } struct BottomView: View { var body: some View { Text("Bottom") } } #Preview { ContentView() }
Oct ’24
Reply to Simple SwiftData app exhibits excessive & persistent memory growth as items are added
Just wanted to close the loop on this…I ended up submitting a dev support ticket and was told that this issue was fixed in iOS 18 (beta at the time). After testing on iOS 18, I confirmed that they did address the lag issue. The good news: the SwiftData version remains responsive, even with 3,200 items. The bad news: it still uses twice the memory as the CoreData version. So I've decided to stay with CoreData in my app until there’s a compelling reason to switch back.
3w