Post

Replies

Boosts

Views

Activity

ViewModifier: Pass in ScenePhase or get from @Environment?
I have two view modifiers that work identically in my tests, but I'm concerned I'm missing some case where they wouldn't. Which is better and why: To pass in the ScenePhase from the parent view or to call it directly from the environment? extension View { func reportPhaseChange(phase: ScenePhase) -> some View { modifier(ReportPhaseChange(phase: phase)) } func reportPhaseChange() -> some View { modifier(ReportPhaseChange2()) } } struct ReportPhaseChange: ViewModifier { var phase:ScenePhase func body(content: Content) -> some View { content.onChange(of: phase) { _, newPhase in switch newPhase { case .active: print("going active") case .background: print("going background") case .inactive: print("going inactive") @unknown default: fatalError() } } } } struct ReportPhaseChange2: ViewModifier { @Environment(\.scenePhase) var phase func body(content: Content) -> some View { content.onChange(of: phase) { _, newPhase in switch newPhase { case .active: print("going active") case .background: print("going background") case .inactive: print("going inactive") @unknown default: fatalError() } } } }
Topic: UI Frameworks SubTopic: SwiftUI
2
0
42
Jul ’25
What is the proper way to format a generic subview view when hoping to have it work with both an @State and an @Bindable?
Let's say you have a protocol that can work with both classes and structs but you want to have a uniform UI to make edits. What is the recommended way to have one view that will take both? App import SwiftUI @main struct ObservationTesterApp: App { var body: some Scene { WindowGroup { ContentView(existence: Existence()) } } } Types import Foundation protocol Dateable { var timestamp:Date { get set } } struct Arrival:Dateable { var timestamp:Date } @Observable class Existence:Dateable { var timestamp:Date init(timestamp: Date) { self.timestamp = timestamp } } extension Existence { convenience init() { self.init(timestamp: Date()) } } ContentView, etc // // ContentView.swift // ObservationTester // // import SwiftUI struct EditDateableView<TimedThing:Dateable>:View { @Binding var timed:TimedThing //note that this currently JUST a date picker //but it's possible the protocol would have more var body:some View { DatePicker("Time To Change", selection: $timed.timestamp) } } #Preview { @Previewable @State var tt = Arrival(timestamp: Date()) EditDateableView<Arrival>(timed: $tt) } struct ContentView: View { @State var arrival = Arrival(timestamp: Date()) @Bindable var existence:Existence var body: some View { //this work around also not allowed. "self is immutable" // let existBinding = Binding<Existence>(get: { existence }, set: { existence = $0 }) VStack { EditDateableView(timed: $arrival) //a Binding cant take a Bindable //EditDateableView<Existence>(timed: $existence) } .padding() } } #Preview { ContentView(existence: Existence()) }
0
0
80
May ’25
Lists, Generics, Views, Navigation Link, SwiftData - ForEach can't pass a binding anymore.
I'm trying out putting most of my business logic in a Protocol that my @Model can conform to, but I'm running into a SwiftUI problem with a Binding that does not get magically offered up like it does when it the subview is not generic. I have a pretty basic List with a ForEach that now can't properly pass to a generic view based on a protocol. When I try to make a binding manually in the row it says that "item is immutable"... but that also doesn't help me with the NavigationLink? Which is seeing the Binding not the ? But before when the subview was concrete to Thing, it took in the and made its own Binding once it hit the view. I'm unclear on precisely where the change happens and what I can do to work around it. Before I go rearchitecting everything... is there a fix to get the NavigationLink to take on the object like before? What needs to be different? I've tried a number of crazy inits on the subview and they all seem to come back to saying either it can't figure out how to pass the type or I'm trying to use the value before it's been initialized. Have I characterized the problem correctly? Thanks! (let me know if I forgot a piece of code, but this should be the List, the Model/Protocol and the subview) import SwiftUI import SwiftData struct ThingsView: View {     @Environment(\.modelContext) var modelContext     @Query var items: [Thing]          var body: some View {         NavigationStack {             List {                 ForEach(items) { item in                     NavigationLink(value: item) {                         VStack(alignment: .leading) {                             Text(item.textInfo)                                 .font(.headline)                                                          Text(item.timestamp.formatted(date: .long, time: .shortened))                         }                     }                 }.onDelete(perform: deleteItems)             }             .navigationTitle("Fliiiing!") //PROBLEM HERE: Cannot convert value of type '(Binding<Thing>) -> EditThingableView<Thing>' to expected argument type '(Thing) -> EditThingableView<Thing>'             .navigationDestination(for: Thing.self, destination: EditThingableView<Thing>.init) #if os(macOS)             .navigationSplitViewColumnWidth(min: 180, ideal: 200) #endif             .toolbar { #if os(iOS)                 ToolbarItem(placement: .navigationBarTrailing) {                     EditButton()                                      } #endif                 ToolbarItem {                     Button(action: addItem) {                         Label("Add Item", systemImage: "plus")                     }                 }                 ToolbarItem {                     Button("Add Samples", action: addSamples)                 }             }         }     }          func addSamples() {         withAnimation {             ItemSDMC.addSamples(context: modelContext)         }     }          private func addItem() {         withAnimation {             let newItem = ItemSDMC("I did a thing!")             modelContext.insert(newItem)         }     }          func deleteItems(_ indexSet:IndexSet) {         withAnimation {             for index in indexSet {                 items[index].delete(from: modelContext)             }         }     } } #Preview {     ThingsView().modelContainer(for: ItemSDMC.self, inMemory: true) } import Foundation import SwiftData protocol Thingable:Identifiable {     var textInfo:String { get set }     var timestamp:Date { get set } } extension Thingable {     var thingDisplay:String {         "\(textInfo) with \(id) at \(timestamp.formatted(date: .long, time: .shortened))"     } } extension Thingable where Self:PersistentModel {     var thingDisplayWithID:String {         "\(textInfo) with modelID \(self.persistentModelID.id) in \(String(describing: self.persistentModelID.storeIdentifier)) at \(timestamp.formatted(date: .long, time: .shortened))"     } } struct ThingLite:Thingable, Codable, Sendable {     var textInfo: String     var timestamp: Date     var id: Int } @Model final class Thing:Thingable {     //using this default value requires writng some clean up logic looking for empty text info.     var textInfo:String = ""     //using this default value would require writing some data clean up functions looking for out of bound dates.     var timestamp:Date = Date.distantPast          init(textInfo: String, timestamp: Date) {         self.textInfo = textInfo         self.timestamp = timestamp     } } extension Thing {     var LiteThing:ThingLite {         ThingLite(textInfo: textInfo, timestamp: timestamp, id: persistentModelID.hashValue)     } } import SwiftUI struct EditThingableView<DisplayItemType:Thingable>: View {     @Binding var thingHolder: DisplayItemType          var body: some View {                  VStack {             Text(thingHolder.thingDisplay)             Form {                 TextField("text", text:$thingHolder.textInfo)                 DatePicker("Date", selection: $thingHolder.timestamp)             }                      } #if os(iOS)         .navigationTitle("Edit Item")         .navigationBarTitleDisplayMode(.inline) #endif     } } //NOTE: First sign of trouble //#Preview { //    @Previewable var myItem = Thing(textInfo: "Example Item for Preview", timestamp:Date()) //    EditThingableView<Thing>(thingHolder: myItem) //}
4
0
113
Apr ’25
Use XcodeProjectPlugin command plugin to generate file AND register it with Xcode
I have a command plugin that can successfully generate files and put them into by an Xcode project folder where I want them to be. It is a twin to a ProjectPlugin that does a similar task. Currently I manually add the generated files to the project using using "File > Add Files to..." That isn't that hard, but is there a way to add the file to the project programmatically? It would be handy to make it more on par with the end state of running the command on a package. Thanks!
0
0
576
Jan ’24
CrossPost w/ AOUSD forum: Autoplay stage metadata not being acknowledged RealityView (RCP Bundle)
The Apple documentation seems to say RealityKit should obey the autoplay metadata, but it doesn't seem to work. Is the problem with my (hand coded) USDA files, the Swift, or something else? Thanks in advance. I can make the animations run with an explicit call to run, but what have I done wrong to get the one cube to autoplay? https://github.com/carlynorama/ExploreVisionPro_AnimationTests import SwiftUI import RealityKit import RealityKitContent struct ContentView: View { @State var enlarge = false var body: some View { VStack { //A ModelEntity, not expected to autoplay Model3D(named: "cube_purple_autoplay", bundle: realityKitContentBundle) //An Entity, actually expected this to autoplay RealityView { content in if let cube = try? await Entity(named: "cube_purple_autoplay", in: realityKitContentBundle) { print(cube.components) content.add(cube) } } //Scene has one cube that should auto play, one that should not. //Neither do, but both will start (as expected) with click. RealityView { content in // Add the initial RealityKit content if let scene = try? await Entity(named: "Scene", in: realityKitContentBundle) { content.add(scene) } } update: { content in // Update the RealityKit content when SwiftUI state changes if let scene = content.entities.first { if enlarge { for animation in scene.availableAnimations { scene.playAnimation(animation.repeat()) } } else { scene.stopAllAnimations() } let uniformScale: Float = enlarge ? 1.4 : 1.0 scene.transform.scale = [uniformScale, uniformScale, uniformScale] } } .gesture(TapGesture().targetedToAnyEntity().onEnded { _ in enlarge.toggle() }) VStack { Toggle("Enlarge RealityView Content", isOn: $enlarge) .toggleStyle(.button) }.padding().glassBackgroundEffect() } } } No autospin meta data #usda 1.0 ( defaultPrim = "transformAnimation" endTimeCode = 89 startTimeCode = 0 timeCodesPerSecond = 24 upAxis = "Y" ) def Xform "transformAnimation" () { def Scope "Geom" { def Xform "xform1" { float xformOp:rotateY.timeSamples = { ... } double3 xformOp:translate = (0, 0, 0) uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateY"] over "cube_1" ( prepend references = @./cube_base_with_purple_linked.usd@ ) { double3 xformOp:translate = (0, 0, 0) uniform token[] xformOpOrder = ["xformOp:translate"] } } With autoplay metadata #usda 1.0 ( defaultPrim = "autoAnimation" endTimeCode = 89 startTimeCode = 0 timeCodesPerSecond = 24 autoPlay = true playbackMode = "loop" upAxis = "Y" ) def Xform "autoAnimation" { def Scope "Geom" { def Xform "xform1" { float xformOp:rotateY.timeSamples = { ... } double3 xformOp:translate = (0, 0, 0) uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateY"] over "cube_1" ( prepend references = @./cube_base_with_purple_linked.usd@ ) { quatf xformOp:orient = (1, 0, 0, 0) float3 xformOp:scale = (2, 2, 2) double3 xformOp:translate = (0, 0, 0) uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"] } } } }
0
0
536
Oct ’23
A non-destructive URL.lines?
I am working on a Server-Sent-Event parser which has newlines as part of the protocol: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation If the line is empty (a blank line) Dispatch the event, as defined below. Unfortunately, when using: let (asyncBytes, _) = try await self.session.bytes(from: sseStreamURL) for try await line in asyncBytes.lines { //parse line } The iterator actually skips empty lines entirely, which seems destructive and therefore can't be the only way? Can it? With split there is a omittingEmptySubsequences: false, but I didn't find any trace of anything like that for .lines. I couldn't find the var definition in the Swift repo either, if anyone could point me at it. I did write something that works for now, but I'd love if anyone had any comments on how to improve it / point me at any exiting language features that I missed. extension AsyncSequence where Element == UInt8 {     var allLines:AsyncThrowingStream<String, Error> {         AsyncThrowingStream { continuation in             Task {                 var buffer:[UInt8] = []                 var iterator = self.makeAsyncIterator()                 while let byte = try await iterator.next() {                     // b/c 10 == \n                     if byte != 10 { buffer.append(byte) } else {                         if buffer.isEmpty { continuation.yield("") } else {                             if let line = String(data: Data(buffer), encoding: .utf8) { continuation.yield(line) } else { //Is there a different AsyncSequence error I could use?                                 throw SSEError("allLines: Couldn't make string from [UInt8] chunk")                             }                             buffer = []                         } }                 }             }         }     } } usage: for try await line in asyncBytes.allLines { //parse line } or of course also var iterator = asyncBytes.allLines.makeAsyncIterator() while let line = try await iterator.next() { //parse line }
2
0
866
Feb ’23
NavigationStack memory leak?
I am running into an example where if you have a View that has a NavigationStack in it, and it appears and disappears based on a conditional, it doesn't seem to get fully torn down? This appears to matter most when it is also initializing a ViewModel, because it will reinit without deiniting. So for example struct ContentView: View { @State var showNavStack:Bool = false var body: some View { Button("ShowHide") { showNavStack.toggle() }.padding(20) if showNavStack { SimpleRootView() } } } With the NavigationStack view import SwiftUI class DoNothingVM:ObservableObject { deinit { print("ViewModel DEINIT") } init() { print("ViewModel INIT") } } struct SimpleRootView: View { @StateObject var viewModel = DoNothingVM() @State var path = NavigationPath() let int = Int.random(in: 0...100) var body: some View { NavigationStack(path: $path) { VStack { Text("Hello \(int)") Button("Go Forward") { path.append(Int.random(in: 0...100)) } }.navigationDestination(for: Int.self) { int in DetailIntView(int: int, path: $path) }.navigationTitle("\(int)") } } } struct DetailIntView:View { let int:Int @Binding var path:NavigationPath var body: some View { VStack { Text("Hello \(int)") Button("Go Forward") { path.append(Int.random(in: 0...100)) } }.navigationTitle("\(int)") } } Will lead to multiple calls to a DoNothing init every time the showNavStack is toggled to true, but no deinit when toggled to false. Multiples remain in memory. "So don't do that" is fine for me in my case, but I'm wondering if this is expected behavior? Is it simply that SwiftUI only expects One and Only One NavigationStack per app and that it will be the only thing in charge? I haven't tested it, but what does that mean for coming back out of the background? This seems like a bug, but it might also be my misunderstanding. Project where I'm messing around with this here: https://github.com/carlynorama/NavigationExplorer
4
2
2.6k
Oct ’22
Example in Charts documentation does not work as shown.
Summary: The "ProfitOverTime" example code from https://developer.apple.com/documentation/charts/chart does not compile, and when changes are made to make it compile the chart concatenates the lines rather than having them be separate. Seen in Xcode-Beta's 14.3 and 14. 4 (14A5284g) Is it simply that this method of layering lines is deprecated already? When trying to have a chart with multiple data sets, I copy-pasted the code from the above link and generated the appropriate placeholder data. I got an error that ProfitOverTime was not identifiable, so I added the id on date. In both the case of the years being identical and of the years being different the lines concatenate instead of overlay like in the screenshot on the docs page. Is this method of multiple lines still available and I'm just doing it wrong? Is only method to have multiple lines series as seen on the LineMark docs page? struct ChartTests: View { struct ProfitOverTime { var date: Date var profit: Double } static func months(forYear year:Int) -> [Date] { var days:[Date] = [] var dateComponets = DateComponents() //let year = Calendar.current.component(.year, from: Date()) dateComponets.year = year dateComponets.day = 1 for i in 1...12 { dateComponets.month = i if let date = Calendar.current.date(from: dateComponets) { days.append(date) } } return days } static func dataBuilder(forYear year:Int) -> [ProfitOverTime]{ var data:[ProfitOverTime] = [] for month in months(forYear: year) { let new = ProfitOverTime(date: month, profit: Double.random(in: 200...600)) data.append(new) } return data } let departmentAProfit: [ProfitOverTime] = Self.dataBuilder(forYear: 2021) let departmentBProfit: [ProfitOverTime] = Self.dataBuilder(forYear: 2021) var body: some View { Chart { ForEach(departmentAProfit, id: \.date) { LineMark( x: .value("Date", $0.date), y: .value("Profit A", $0.profit) ) .foregroundStyle(.blue) } ForEach(departmentBProfit, id: \.date) { LineMark( x: .value("Date", $0.date), y: .value("Profit B", $0.profit) ) .foregroundStyle(.green) } RuleMark( y: .value("Threshold", 500.0) ) .foregroundStyle(.red) } } } struct ChartTests_Previews: PreviewProvider { static var previews: some View { ChartTests() } }
5
0
1.8k
Aug ’22