Post

Replies

Boosts

Views

Activity

Reply to Print a SwiftUI view on macOS?
No idea. I've implemented a workaround by creating a PDF. This works well for me. Here is some sample code: func savePDF(docName : String?) { guard let saveURL = showSavePDFPanel(docName) else { return } var mediaBox = NSRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 1100, height: 600)) if let dataConsumer = CGDataConsumer(url: saveURL as CFURL) { if let pdfContext = CGContext(consumer: dataConsumer, mediaBox: &mediaBox, nil) { let options: [CFString: Any] = [kCGPDFContextMediaBox: mediaBox] for sem in ItrSemester.allCases { pdfContext.beginPDFPage(options as CFDictionary) let renderer = ImageRenderer(content: MyView().environmentObject(appData)) renderer.render { size, renderFunction in pdfContext.translateBy(x: (mediaBox.width - size.width) / 2.0, y: (mediaBox.height - size.height) / 2.0) renderFunction(pdfContext) } /// Add a day header to each page with document name let titleString = "\(docName ?? "Ohne Titel")" let attrs : [NSAttributedString.Key : Any] = [.font: NSFont.boldSystemFont(ofSize: 16.0)] let day = NSAttributedString(string: titleString, attributes: attrs) let path = CGMutablePath() let strWidth = day.size().width + 1 let strHeight = day.size().height + 1 let dayXPos = 20.0 let dayYPos = mediaBox.height - strHeight - 32 path.addRect(CGRect(x: dayXPos, y: dayYPos, width: strWidth, height: strHeight)) let fSetter = CTFramesetterCreateWithAttributedString(day as CFAttributedString) let frame = CTFramesetterCreateFrame(fSetter, CFRangeMake(0, day.length), path, nil) CTFrameDraw(frame, pdfContext) pdfContext.endPDFPage() } pdfContext.closePDF() } } }
Mar ’24
Reply to "reset" users home dir to archived content
Meanwhile I got a solution I want to share here: It seems so that the OS has some "voodoo" to protect used home directories. So a change of users home dir via dscl . -change /Users/USER NFSHomeDirectory /Users/USER /doesNotExist allows removing the HOMEDIR and re-create it from a template. Afterwards the command dscl . -change /Users/USER NFSHomeDirectory /doesNotExist /Users/USER does set the homedir back to the old value.
Feb ’24
Reply to SwiftUI alert buttons order issue
How about using no role at all? Without a role there is no reason for the OS to rearrange the buttons. Something like .alert( "Test alert", isPresented: $isAlertPresented, actions: { Button("Default role", action: { }) Button("Cancel role", role: .none, action: { isAlertPresented = false }) }, message: { Text("Test message") }
Dec ’23
Reply to Swift Regex too slow als grep replacement?
I note that none of your regular expressions actually need to be regular expressions, i.e. they are just fixed text strings. For example, where you have "non.sense", I think you only want it to literally match "non.sense", not "non0sense". Yes. The first two filters are fixed strings but the last one contains a lot of different search strings. Therefore RegEx makes sense. You're right that a simple string search may be faster but since it takes now just 3 seconds I'm fine with it. Thanks for sharing your thoughts on my code.
Jul ’23
Reply to Swift Regex too slow als grep replacement?
There was another discussion on https://forums.swift.org/t/use-regexbuilder-als-grep-replacement/65782 where I was able to get much faster code. This code does now run in 3 seconds. let paths = ["regTest/logs/access.log", "/regTest/logs/access.log.1"] var startDate = Date() let matchedLines = try await withThrowingTaskGroup(of: [String].self, body: { group in for aPath in paths { group.addTask { let lines = FileHandle(forReadingAtPath: aPath)!.bytes.lines .filter { $0.contains("26/Apr") } .filter { $0.contains("\"GET ") } .filter{ !$0.contains(botListRegEx) } var matched : [String] = [] for try await line in lines { if let m = line.firstMatch(of: ipAtStartRegEx) { let (s, _) = m.output matched.append(String(s)) } } return matched } } var matchedLines : [String] = [] for try await partialMatchedLines in group { matchedLines.append(contentsOf: partialMatchedLines) } return matchedLines }) let uniqArray = Set(matchedLines) print("Match time: \(abs(startDate.timeIntervalSinceNow)) s") print("Found \(matchedLines.count) lines.") print("Found \(uniqArray.count) IP adresses.")
Jun ’23
Reply to Swift Regex too slow als grep replacement?
Good catch. Did you also change the definition of the regex to remove the Capture { } ? Nope. I did just replace "firstMatch". Now I used a plain string and it is much faster. Also I use now another split which is way faster (20 vs. 50s). So here is the code let matchedLines = fullText.split(whereSeparator: \.isNewline) .filter{ $0.contains(yesterdayRegEx) } .filter{ $0.contains(botListRegEx) } Match time: 52.7 s let matchedLines = fullText.split(whereSeparator: \.isNewline) .filter{ $0.contains("26/Apr") } .filter{ $0.contains(botListRegEx) } Match time: 21.8 s So the slow part is now the Split which take most of the time (21 s).
Jun ’23
Reply to Swift Regex too slow als grep replacement?
The Swift code is fast for everything but the RegEx. The file read takes about 1 s but the RegEx takes more than 80 s. That's why I ask for help to speed-up the RegEx. My Swift sample code does no sort and uniq as in the shell example. This will be added after the RegEx is usable. I won´t care if the code in not quite so fast as my shell but it must be much quicker to be usable.
Jun ’23
Reply to Random SwiftUI crashes
Here is a smaller version which does also crash (now tested with new Xcode 14.3): import SwiftUI struct vTime : Codable { var hour : Int var min : Int } struct RecDate : Codable { var day : Int var start: vTime var end : vTime private enum CodingKeys : String, CodingKey { case day case start case end } func duration() -> CGFloat { let dTime : CGFloat = abs(CGFloat(end.hour - start.hour) * 4.0 + CGFloat(end.min - start.min) / 15.0) return dTime } } class Record : Hashable, Equatable, Identifiable, ObservableObject { let vID = UUID() @Published var subject : String @Published var date : RecDate init(subject: String, date : RecDate) { self.subject = subject self.date = date } static func == (lhs: Record, rhs: Record) -> Bool { var r = lhs.subject.compare(rhs.subject) == .orderedSame r = r && lhs.date.start.hour == rhs.date.start.hour r = r && lhs.date.start.min == rhs.date.start.min r = r && lhs.date.end.hour == rhs.date.end.hour r = r && lhs.date.end.min == rhs.date.end.min r = r && lhs.vID == rhs.vID return r } func hash(into hasher: inout Hasher) { hasher.combine(subject) hasher.combine(date.start.hour) hasher.combine(date.start.min) hasher.combine(date.end.hour) hasher.combine(date.end.min) hasher.combine(vID) } func isSelected(appSelection : UUID?) -> Bool { return appSelection == vID } public func incDate() { if !(self.date.end.hour == 19 && self.date.end.min == 0) { self.date.start.min += 15 self.date.end.min += 15 if self.date.start.min == 60 { self.date.start.min = 0 self.date.start.hour += 1 } if self.date.end.min == 60 { self.date.end.min = 0 self.date.end.hour += 1 } } } public func decDate() { if !(self.date.start.hour == 8 && self.date.start.min == 0) { self.date.start.min -= 15 self.date.end.min -= 15 if self.date.start.min < 0 { self.date.start.min = 45 self.date.start.hour -= 1 } if self.date.end.min < 0 { self.date.end.min = 45 self.date.end.hour -= 1 } } } } public class RecordHandler : ObservableObject { @Published var records : [Record] = [ Record(subject: "banana", date: RecDate(day: 0, start: vTime(hour: 8, min: 0), end: vTime(hour: 10, min: 0))), Record(subject: "strawberry", date: RecDate(day: 1, start: vTime(hour: 11, min: 0), end: vTime(hour: 14, min: 0))) ] @Published var selectedID : UUID? = nil func setSel(newID : UUID) { if selectedID == nil { selectedID = newID } else { if selectedID == newID { selectedID = nil } else { selectedID = newID } } } } struct EntryView: View { @EnvironmentObject var appData : RecordHandler @ObservedObject var setup : Record @State private var showDataView : Bool = false struct SelView : View { var body: some View { Rectangle() .inset(by: -2) .stroke(.blue, lineWidth: 5.0) } } var body: some View { let qCount = setup.date.duration() let height = qCount * 10.0 let xPos = 100.0 let yPos = 7.0 + (CGFloat(setup.date.start.hour) - 8.0) * 40.0 + CGFloat(setup.date.start.min) / 1.5 + (qCount - 1.0) * 5.0 ZStack { RoundedRectangle(cornerRadius: 10) .foregroundColor(.red.opacity(0.80)) Text(setup.subject) if setup.isSelected(appSelection: appData.selectedID) { SelView() } } .frame(width: 200, height: height) .position(x: xPos, y: yPos) .onTapGesture(count: 1) { appData.setSel(newID: setup.vID) } } } struct DayView: View { @EnvironmentObject var appData : RecordHandler var day : Int var body: some View { VStack { ForEach(appData.records, id: \.self) { value in if value.date.day == day { EntryView(setup: value) .environmentObject(appData) } } } } } struct ContentView: View { @EnvironmentObject var appData : RecordHandler var body: some View { NavigationStack { HStack { DayView(day: 0) .environmentObject(appData) DayView(day: 1) .environmentObject(appData) } } .toolbar { ToolbarItemGroup(placement: .automatic) { Button("↑") { if let ds = appData.records.first(where: { $0.vID == appData.selectedID!}) { ds.decDate() } } .disabled(appData.selectedID == nil) Button("↓") { if let ds = appData.records.first(where: { $0.vID == appData.selectedID!}) { ds.incDate() } } .disabled(appData.selectedID == nil) } } } } struct ContentView_Previews: PreviewProvider { @StateObject static var previewData = RecordHandler() static var previews: some View { ContentView() .environmentObject(previewData) } } @main struct crasherApp: App { @StateObject private var appData = RecordHandler() var body: some Scene { WindowGroup { ContentView() .environmentObject(appData) } } }
Mar ’23
Reply to Random SwiftUI crashes
import SwiftUI struct vTime : Codable { var hour : Int var min : Int } struct RecDate : Codable { var day : Int var start: vTime var end : vTime private enum CodingKeys : String, CodingKey { case day case start case end } func duration() -> CGFloat { let dTime : CGFloat = abs(CGFloat(end.hour - start.hour) * 4.0 + CGFloat(end.min - start.min) / 15.0) return dTime } } class Record : Hashable, Equatable, Identifiable, ObservableObject { let vID = UUID() @Published var subject : String @Published var date : RecDate init(subject: String, date : RecDate) { self.subject = subject self.date = date } static func == (lhs: Record, rhs: Record) -> Bool { var r = lhs.subject.compare(rhs.subject) == .orderedSame r = r && lhs.date.start.hour == rhs.date.start.hour r = r && lhs.date.start.min == rhs.date.start.min r = r && lhs.date.end.hour == rhs.date.end.hour r = r && lhs.date.end.min == rhs.date.end.min r = r && lhs.vID == rhs.vID return r } func hash(into hasher: inout Hasher) { hasher.combine(subject) hasher.combine(date.start.hour) hasher.combine(date.start.min) hasher.combine(date.end.hour) hasher.combine(date.end.min) hasher.combine(vID) } func isSelected(appSelection : UUID?) -> Bool { return appSelection == vID } public func incDate() { if !(self.date.end.hour == 19 && self.date.end.min == 0) { self.date.start.min += 15 self.date.end.min += 15 if self.date.start.min == 60 { self.date.start.min = 0 self.date.start.hour += 1 } if self.date.end.min == 60 { self.date.end.min = 0 self.date.end.hour += 1 } } } public func decDate() { if !(self.date.start.hour == 8 && self.date.start.min == 0) { self.date.start.min -= 15 self.date.end.min -= 15 if self.date.start.min < 0 { self.date.start.min = 45 self.date.start.hour -= 1 } if self.date.end.min < 0 { self.date.end.min = 45 self.date.end.hour -= 1 } } } } public class RecordHandler : ObservableObject { @Published var records : [Record] = [ Record(subject: "banana", date: RecDate(day: 0, start: vTime(hour: 8, min: 0), end: vTime(hour: 10, min: 0))), Record(subject: "strawberry", date: RecDate(day: 1, start: vTime(hour: 11, min: 0), end: vTime(hour: 14, min: 0))) ] @Published var selectedID : UUID? = nil func updateRecord(oldID : UUID?, newV : Record) { self.records.removeAll { value in value.vID == oldID } self.records.append(newV) } func setSel(newID : UUID) { if selectedID == nil { selectedID = newID } else { if selectedID == newID { selectedID = nil } else { selectedID = newID } } } } struct EntryView: View { @EnvironmentObject var appData : RecordHandler @ObservedObject var setup : Record @State private var showDataView : Bool = false struct SelView : View { var body: some View { ZStack { Rectangle() .inset(by: -2) .stroke(.blue, lineWidth: 5.0) } } } var body: some View { let qCount = setup.date.duration() let height = qCount * 10.0 let xPos = 100.0 let yPos = 7.0 + (CGFloat(setup.date.start.hour) - 8.0) * 40.0 + CGFloat(setup.date.start.min) / 1.5 + (qCount - 1.0) * 5.0 ZStack { RoundedRectangle(cornerRadius: 10) .foregroundColor(.red.opacity(0.80)) VStack { Text(setup.subject) .bold() .allowsTightening(true) .multilineTextAlignment(.center) .truncationMode(.tail) } .font(.system(size: 10)) .padding([.leading, .trailing], 5) if setup.isSelected(appSelection: appData.selectedID) { SelView() } } .frame(width: 200, height: height) .position(x: xPos, y: yPos) .onTapGesture(count: 1) { appData.setSel(newID: setup.vID) } } } struct RoomView: View { @EnvironmentObject var appData : RecordHandler var body: some View { VStack() { VStack { ForEach(appData.records, id: \.self) { value in EntryView(setup: value) .environmentObject(appData) } } } } } struct DayView: View { @EnvironmentObject var appData : RecordHandler var day : Int var body: some View { VStack { ForEach(appData.records, id: \.self) { value in if value.date.day == day { EntryView(setup: value) .environmentObject(appData) } } } } } struct ContentView: View { @EnvironmentObject var appData : RecordHandler var body: some View { NavigationStack { HStack { DayView(day: 0) .environmentObject(appData) DayView(day: 1) .environmentObject(appData) } } .toolbar { ToolbarItemGroup(placement: .automatic) { Button("↑") { if let ds = appData.records.first(where: { $0.vID == appData.selectedID!}) { ds.decDate() } } .disabled(appData.selectedID == nil) Button("↓") { if let ds = appData.records.first(where: { $0.vID == appData.selectedID!}) { ds.incDate() } } .disabled(appData.selectedID == nil) } } } } struct ContentView_Previews: PreviewProvider { @StateObject static var previewData = RecordHandler() static var previews: some View { ContentView() .environmentObject(previewData) } } @main struct crasherApp: App { @StateObject private var appData = RecordHandler() var body: some Scene { WindowGroup { ContentView() .environmentObject(appData) } } }
Mar ’23
Reply to Random SwiftUI crashes
Now I was able to build a (not so small) example which shows the issue. It shows two rectangles which can be selected. After selection it is possible to move them via the toolbar buttons. Click a rectangle and move it up and down. Change selection and repeat. After some tries it will crash. I post the example in the next post because the message size limit is reached.
Mar ’23
Reply to Fatal error: Duplicate keys of type 'SOMETYPE' were found in a Dictionary.
I did post the same question on Swift Forum and got a solution: Currently your hash and == are using completely different properties, which leads to conflict between them. Are two Vorlesung with the same subject but different vID equal to each other, or not? According to == they are equal, but according to hash they are not. We need to fix that You need to decide what makes two Vorlesung the same, and use these properties in both == and hash. Usually in a struct it's all of the properties, and that's handled by the autogenerated implementation so a change in my code did solve the issue: static func == (lhs: Vorlesung, rhs: Vorlesung) -> Bool { let result = lhs.subject.compare(rhs.subject) == .orderedSame return result } func hash(into hasher: inout Hasher) { hasher.combine(subject) }
Mar ’23