Post

Replies

Boosts

Views

Activity

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()     }   }   }
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)") } }
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
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)") } }
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
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 :
Aug ’22