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()
}
}
}
Post
Replies
Boosts
Views
Activity
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.
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")
}
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.
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.")
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).
.filter{ $0.contains(yesterdayRegEx) }
.filter{ $0.contains(botListRegEx) }
Makes no difference.
But now I got another idea: I removed the filter to get a clue how much time the spilt needs. It is about 50 s! Therefore the split is the major slowdown and the RegEx take just 30 s.
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.
To answer my own question:
There is currently no "SwiftUI like“ way to handle this.
So use just the old way via AppDelegate:
final class AppDelegate: NSObject, NSApplicationDelegate {
func application(_ application: NSApplication, open urls: [URL]) {
// Read the provided URLs
}
}
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)
}
}
}
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)
}
}
}
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.
I need to create a simplified example which takes some time…
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)
}
Using above code I got still the same error although it needs more clicks until it occurred.
I wonder if there is a general issue in SwiftUI or in my data model.