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()
} } }
Post
Replies
Boosts
Views
Activity
Underlying SwiftUI code has probably changed since then but:
List(0...10, id:\.self, selection: $selection) { item in
Label("Item: \(item)", systemImage: "chevron.right")
}
would possibly have also fixed it.
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)")
}
}
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
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)")
}
}
Feedback: FB11643551
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
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
:
Feedback Assistant No: FB11061755
Also, conforming the struct ProfitOverTime to Identifiable by adding an id = UUID() has the same result
FWIW, why I care is this project - I want my recommended curve and edited curve to both be displayed. https://github.com/carlynorama/DataViewer/blob/main/DataViewer/DisplayView.swift