Post

Replies

Boosts

Views

Activity

Reply to ViewModifier: Pass in ScenePhase or get from @Environment?
Thanks! Good to know they're both valid. Since this is in a ViewModifier, which couldn't attach to a WindowGroup, there might be some protection? But you never know. Good to a mention in any documentation I write for it. Perhaps then it is safer to not let one be passed in? Presumably any Views the ViewModifier would add (like a conditional overlay) would have their own ScenePhases, but would there ever be a situation where the modifier and the view it modifies would have different ScenePhases? Is a view modifier in the same Scene as it's parent view or it's own thing?
Topic: UI Frameworks SubTopic: SwiftUI
Jul ’25
Reply to Lists, Generics, Views, Navigation Link, SwiftData - ForEach can't pass a binding anymore.
So I gave .navigationDestination a concrete view with the idea that I'd put the generic in that as wrapper to see how that went, and now I'm not 100% on how to clear up the new Cannot convert value of type 'Bindable<Thing>' to expected argument type 'Binding<Thing>' error. The generic view has to be a Binding not a Bindable because the protocol might be pointed at a @State of a struct instance of Thingable in some future use of this reuasble view? That's the whole point of trying this. struct EditThingView: View {     @Bindable var thing:Thing          var body: some View { //Cannot convert value of type 'Bindable<Thing>' to expected argument type 'Binding<Thing>'         EditThingableView<Thing>(thingHolder: $thing)     } } This kind of glue view could be a path, maybe? I'll update if I get it any further. ETA: For what its worth it appears to be easy peasy to pass fragments of the @Bindable to a subview with generics (at least StringProtocol) just not the whole enchilada. (Which is still what I need but a data point) struct TestFieldView<S:StringProtocol>:View {     @Binding var fieldText:S          var body: some View {         let castBinding = Binding<String>(get: { String(fieldText) },                                           set: { fieldText = $0 as? S ?? "" })         TextField("text", text:castBinding)     } } struct EditThingView: View {     @Bindable var thing:Thing               var body: some View {         VStack {             Text(thing.thingDisplay)             Form {                 TestFieldView(fieldText: $thing.textInfo)             }         }     } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Apr ’25
Reply to Lists, Generics, Views, Navigation Link, SwiftData - ForEach can't pass a binding anymore.
Changing my example to more closely match the included.. Adding @State private var selectedItem: Thing? changing the navigationDestination to .navigationDestination(item: $selectedItem) { item in                 EditThingableView<Thing>(thingHolder: item)             } Still leaves me with a "Cannot convert value of type 'Thing' to expected argument type 'Binding'" Error. That like before does not happen when the the subview is not generic. I'll keep hunting, Thanks!
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Apr ’25
Reply to A non-destructive URL.lines?
Didn't want to leave that code up without the task closer: extension AsyncSequence where Element == UInt8 {     //Works.     var allLines_v1:AsyncThrowingStream<String, Error> {         AsyncThrowingStream { continuation in             let bytesTask = Task {                 var accumulator:[UInt8] = []                 var iterator = self.makeAsyncIterator()                 while let byte = try await iterator.next() {                     //10 == \n                     if byte != 10 { accumulator.append(byte) }                     else {                         if accumulator.isEmpty { continuation.yield("") }                         else {                             if let line = String(data: Data(accumulator), encoding: .utf8) { continuation.yield(line) }                             else { throw MastodonAPIError("allLines: Couldn't make string from [UInt8] chunk") }                             accumulator = []             }   }   }   }             continuation.onTermination = { @Sendable _ in                 bytesTask.cancel()     }   }   }
Topic: Programming Languages SubTopic: Swift Tags:
Feb ’23
Reply to Multiple NavigationStack In NavigationSplitView Issue
I have an example in an unrelated reply (https://developer.apple.com/forums/thread/716804?answerId=731260022#731260022) that does much of what you are trying to do? If I have time I'll try to rework your example code but in the mean time I hope this helps: Some notes: I'm not sure why your selection is a set of one instead of an Ocean? That is the biggest ?? to me. Are you trying to pass a deep link? In which case why aren't you setting the path with it? If you're trying to manage deep links I'd recommend making a NavigationManager / Coordinator / Store class that lives all the way at the top. (Ocean will need to be Hashable to use it as an .id() parameter) struct SplitViewLanding: View { var options = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon"] //NOTE: the selection binding is an an optional. @State var selection:String? var body: some View { NavigationSplitView { Button("Nil Selection") { selection = nil } List(options, id:\.self, selection: $selection) { o in Text("\(o)") } } detail: { if let selection { RootForDetailView(for: selection).id(selection) } } } } class DetailDoNothingVM:ObservableObject { @Published var optionSet:String deinit { print("DetailDoNothingVM DEINIT") } init() { print("DetailDoNothingVM INIT") self.optionSet = "Default" } } struct RootForDetailView: View { @StateObject var viewModel = DetailDoNothingVM() let optionSet:String init(for optionSet:String) { self.optionSet = optionSet } @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 DetailOptionIntView(int: int, path: $path).environmentObject(viewModel) }.navigationTitle("\(viewModel.optionSet): \(int)") }.onAppear() { viewModel.optionSet = optionSet } } } struct DetailOptionIntView:View { let int:Int @Binding var path:NavigationPath @EnvironmentObject var viewModel:DetailDoNothingVM var body: some View { VStack { Text("Hello \(int)") Button("Go Forward") { path.append(Int.random(in: 0...100)) } }.navigationTitle("\(viewModel.optionSet):\(int)") } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Oct ’22
Reply to NavigationStack memory leak?
This stack overflow question seems to show similar behavior when the flow control is a switch statement not an if statement. It's been added to the sample app. https://stackoverflow.com/questions/73941284/why-are-objects-still-in-memory-after-emptying-navigationstack-path/73954020
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Oct ’22
Reply to NavigationStack memory leak?
As a confirmation test I embed the NavigationStack in a SplitView and verified the viewModels do deint when the view is removed from the screen (forcibly with a .id() call), so memory clean up happens correctly within the Navigation ecosystem. struct SplitViewLanding: View { var options = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon"] //NOTE: the selection binding MUST be an optional. @State var selection:String? var body: some View { NavigationSplitView { //This button removes the view from the screen but the DEINIT is not called until a new view is selected to replace it. A hint to how the memory leak is happening when the NavigationStack is not managed by the Navigation framework. Button("Nil Selection") { selection = nil } List(options, id:\.self, selection: $selection) { o in Text("\(o)") } } detail: { if let selection { //inits and deinits viewModel RootForDetailView(for: selection).id(selection) //inits once and then updates vm with the current selection IF AND ONLY IF you have code to do so "onAppear" //RootForDetailView(for: selection) } } } } class DetailDoNothingVM:ObservableObject { @Published var optionSet:String deinit { print("DetailDoNothingVM DEINIT") } init() { print("DetailDoNothingVM INIT") self.optionSet = "default" } } struct RootForDetailView: View { @StateObject var viewModel = DetailDoNothingVM() let optionSet:String init(for optionSet:String) { self.optionSet = optionSet } @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 DetailOptionIntView(int: int, path: $path).environmentObject(viewModel) }.navigationTitle("\(int)") }.onAppear() { //Necessary to update model if you don't mark the call to this view with a .id because the init will only be called once viewModel.optionSet = optionSet } } } struct DetailOptionIntView:View { let int:Int @Binding var path:NavigationPath @EnvironmentObject var viewModel:DetailDoNothingVM var body: some View { VStack { Text("Hello \(int)") Button("Go Forward") { path.append(Int.random(in: 0...100)) } }.navigationTitle("\(viewModel.optionSet):\(int)") } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Oct ’22
Reply to How to create and store the pdf file
Just because I found this answer when looking for something similar - Putting the accepted solution into an extension on CGContext makes it handy to reuse. extension CGContext { static func pdf(size: CGSize, render: (CGContext) -> ()) -> Data { let pdfData = NSMutableData() let consumer = CGDataConsumer(data: pdfData)! var mediaBox = CGRect(origin: .zero, size: size) let pdfContext = CGContext(consumer: consumer, mediaBox: &mediaBox, nil)! pdfContext.beginPage(mediaBox: &mediaBox) render(pdfContext) pdfContext.endPage() pdfContext.closePDF() return pdfData as Data } } which can be called like: func render() -> Data { let size = CGSize(width: 600, height: 400) return CGContext.pdf(size: size) { context in //some drawing operations for example context.saveGState() context.setFillColor(NSColor.red.cgColor) context.addPath(shape.path(in: CGRect(origin: .zero, size: size))) context.fillPath() context.restoreGState() // end example } } And then put in a view like: struct ContentView: View { var body: some View { Image(nsImage: NSImage(data: render())!) .frame(maxWidth: .infinity, maxHeight: .infinity) } } or written to file like the selected answer let location = URL(string: "/somewhere/")! do { try (render()).write(to: location, options: .atomic) } catch { print(error) } FurtherReading: https://developer.apple.com/documentation/pdfkit/custom_graphics https://developer.apple.com/documentation/uikit/uigraphicspdfrenderer sample code from: https://talk.objc.io/episodes/S01E225-view-protocols-and-shapes (they use but don't show the extension, it's in the linked repo) https://gist.github.com/KrisYu/83d7d97cae35a0b10fd238e5c86d288f https://stackoverflow.com/questions/63625085/drawing-text-to-a-cgcontext-for-quartz-pdf-not-working
Topic: Programming Languages SubTopic: Swift Tags:
Aug ’22
Reply to Example in Charts documentation does not work as shown.
Example code that works from the updated page (I should add that this code leaves in the fact that I made ProfitOverTime conform to Identifiable and the new code on the documentation page uses ForEach(departmentAProfit, id: \.date) instead.) import SwiftUI import Charts struct ChartTests: View { struct ProfitOverTime:Identifiable { var date: Date var profit: Double var id = UUID() } 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) { LineMark( x: .value("Date", $0.date), y: .value("Profit A", $0.profit), series: .value("Company", "A") ) .foregroundStyle(.blue) } ForEach(departmentBProfit) { LineMark( x: .value("Date", $0.date), y: .value("Profit B", $0.profit), series: .value("Company", "B") ) .foregroundStyle(.green) } RuleMark( y: .value("Threshold", 500.0) ) .foregroundStyle(.red) } } } struct ChartTests_Previews: PreviewProvider { static var previews: some View { ChartTests() } } Screenshot :
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’22