diff options
| author | Ruben Beltran del Rio <ruben@unlimited.pizza> | 2021-02-06 00:55:04 +0100 |
|---|---|---|
| committer | Ruben Beltran del Rio <ruben@unlimited.pizza> | 2021-02-06 00:55:04 +0100 |
| commit | 75a0e4509a70055851b085f3f7293ae1cf48164c (patch) | |
| tree | b2933b23235b83cb9f5f2d9fd42c8028650ad1d6 /Map | |
| parent | 91fd86189477e6c690c6487d51d80bc58c8ecb63 (diff) | |
Fix performance and undo1.2.0
Diffstat (limited to 'Map')
| -rw-r--r-- | Map/Debouncer.swift | 2 | ||||
| -rw-r--r-- | Map/Info.plist | 2 | ||||
| -rw-r--r-- | Map/MapParser/MapParser.swift | 3 | ||||
| -rw-r--r-- | Map/State/AppState.swift | 5 | ||||
| -rw-r--r-- | Map/Views/ContentView.swift | 16 | ||||
| -rw-r--r-- | Map/Views/MapDetail.swift | 67 | ||||
| -rw-r--r-- | Map/Views/MapRender.swift | 20 | ||||
| -rw-r--r-- | Map/Views/MapTextEditor.swift | 1 | ||||
| -rw-r--r-- | Map/Views/SlowMapRender.swift | 90 |
9 files changed, 158 insertions, 48 deletions
diff --git a/Map/Debouncer.swift b/Map/Debouncer.swift index f119d8a..cd7960a 100644 --- a/Map/Debouncer.swift +++ b/Map/Debouncer.swift @@ -3,7 +3,7 @@ import Foundation class Debouncer { // MARK: - Properties - private let queue = DispatchQueue.main + private let queue = DispatchQueue.global(qos: .utility) private var workItem = DispatchWorkItem(block: {}) private var interval: TimeInterval diff --git a/Map/Info.plist b/Map/Info.plist index 903c15d..6ba8cf0 100644 --- a/Map/Info.plist +++ b/Map/Info.plist @@ -17,7 +17,7 @@ <key>CFBundleShortVersionString</key> <string>$(MARKETING_VERSION)</string> <key>CFBundleVersion</key> - <string>1</string> + <string>$(CURRENT_PROJECT_VERSION)</string> <key>LSApplicationCategoryType</key> <string>public.app-category.productivity</string> <key>LSMinimumSystemVersion</key> diff --git a/Map/MapParser/MapParser.swift b/Map/MapParser/MapParser.swift index 3528eb5..cce6251 100644 --- a/Map/MapParser/MapParser.swift +++ b/Map/MapParser/MapParser.swift @@ -25,6 +25,9 @@ struct ParsedMap { let blockers: [Blocker] let opportunities: [Opportunity] let stages: [CGFloat] + + static let empty: ParsedMap = ParsedMap( + vertices: [], edges: [], blockers: [], opportunities: [], stages: defaultDimensions) } struct Vertex { diff --git a/Map/State/AppState.swift b/Map/State/AppState.swift index f062051..c261725 100644 --- a/Map/State/AppState.swift +++ b/Map/State/AppState.swift @@ -38,7 +38,8 @@ func appStateReducer(state: inout AppState, action: AppAction) { window.makeKeyAndOrderFront(nil) let renderView = MapRenderView( - content: map.content ?? "", evolution: Stage.stages(state.selectedEvolution)) + content: Binding.constant(map.content ?? ""), + evolution: Binding.constant(Stage.stages(state.selectedEvolution))) let view = NSHostingView(rootView: renderView) window.contentView = view @@ -91,7 +92,7 @@ func appStateReducer(state: inout AppState, action: AppAction) { print("Cancel") } } - case .deleteMap(map: let map): + case .deleteMap(let map): let context = PersistenceController.shared.container.viewContext context.delete(map) diff --git a/Map/Views/ContentView.swift b/Map/Views/ContentView.swift index f439026..534c14c 100644 --- a/Map/Views/ContentView.swift +++ b/Map/Views/ContentView.swift @@ -25,7 +25,9 @@ struct ContentView: View { DefaultMapView() } ForEach(maps) { map in - NavigationLink(destination: MapDetailView(map: map)) { + NavigationLink( + destination: MapDetailView(map: map, title: map.title ?? "", content: map.content ?? "") + ) { HStack { Text(map.title ?? "Untitled Map") Spacer() @@ -38,10 +40,12 @@ struct ContentView: View { .cornerRadius(2.0) }.padding(.leading, 8.0) }.contextMenu { - Button(action: { store.send(.deleteMap(map: map))}) { - Image(systemName: "trash") - Text("Delete") - } + Button( + action: { store.send(.deleteMap(map: map)) }, + label: { + Image(systemName: "trash") + Text("Delete") + }) } } .onDelete(perform: deleteMaps) @@ -56,7 +60,7 @@ struct ContentView: View { } } } - DefaultMapView() + DefaultMapView() } } diff --git a/Map/Views/MapDetail.swift b/Map/Views/MapDetail.swift index 6ba92bb..bebe3a1 100644 --- a/Map/Views/MapDetail.swift +++ b/Map/Views/MapDetail.swift @@ -5,9 +5,25 @@ // Created by Ruben Beltran del Rio on 2/1/21. // +import Combine import CoreData import SwiftUI +class SaveTimer { + let currentTimePublisher = Timer.TimerPublisher(interval: 1, runLoop: .main, mode: .default) + let cancellable: AnyCancellable? + + init() { + self.cancellable = currentTimePublisher.connect() as? AnyCancellable + } + + deinit { + self.cancellable?.cancel() + } +} + +let timer = SaveTimer() + struct MapDetailView: View { @Environment(\.managedObjectContext) private var viewContext @Environment(\.colorScheme) var colorScheme @@ -20,23 +36,8 @@ struct MapDetailView: View { MapColor.colorForScheme(colorScheme) } - private var title: Binding<String> { - Binding( - get: { map.title ?? "" }, - set: { title in - map.title = title - } - ) - } - - private var content: Binding<String> { - Binding( - get: { map.content ?? "" }, - set: { content in - map.content = content - } - ) - } + @State var title: String + @State var content: String var body: some View { if map.uuid != nil { @@ -44,10 +45,7 @@ struct MapDetailView: View { VStack { HStack { TextField( - "Title", text: title, - onCommit: { - try? viewContext.save() - } + "Title", text: $title ).font(.title2).textFieldStyle(PlainTextFieldStyle()).padding(.vertical, 4.0).padding( .leading, 4.0) Button(action: saveText) { @@ -60,25 +58,34 @@ struct MapDetailView: View { EvolutionPicker() ZStack(alignment: .topLeading) { - MapTextEditor(text: content, colorScheme: colorScheme).onChange(of: map.content) { _ in - try? viewContext.save() - } - .background(mapColor.background) - .foregroundColor(mapColor.foreground) - .frame(minHeight: 96.0) + MapTextEditor(text: $content, colorScheme: colorScheme) + .background(mapColor.background) + .foregroundColor(mapColor.foreground) + .frame(minHeight: 96.0) }.padding(.top, 8.0).padding(.leading, 8.0).background(mapColor.background).cornerRadius( 5.0) }.padding(.horizontal, 8.0) ScrollView([.horizontal, .vertical]) { - MapRenderView( - content: content.wrappedValue, evolution: Stage.stages(store.state.selectedEvolution)) + SlowMapRender( + content: content, evolution: Stage.stages(store.state.selectedEvolution), + colorScheme: colorScheme) }.background(mapColor.background) + }.onReceive(timer.currentTimePublisher) { _ in + saveModel() + }.onDisappear { + saveModel() } } else { DefaultMapView() } } + private func saveModel() { + map.content = content + map.title = title + try? viewContext.save() + } + private func saveText() { store.send(.exportMapAsText(map: map)) } @@ -90,7 +97,7 @@ struct MapDetailView: View { struct MapDetailView_Previews: PreviewProvider { static var previews: some View { - MapDetailView(map: Map()).environment( + MapDetailView(map: Map(), title: "", content: "").environment( \.managedObjectContext, PersistenceController.preview.container.viewContext) } } diff --git a/Map/Views/MapRender.swift b/Map/Views/MapRender.swift index 7623b61..aa97e22 100644 --- a/Map/Views/MapRender.swift +++ b/Map/Views/MapRender.swift @@ -14,8 +14,10 @@ struct MapRenderView: View { @Environment(\.colorScheme) var colorScheme - var content: String - let evolution: Stage + @Binding var content: String + @Binding var evolution: Stage + + @State var parsedMap: ParsedMap = ParsedMap.empty let mapSize = CGSize(width: 1300.0, height: 1000.0) @@ -23,10 +25,6 @@ struct MapRenderView: View { let vertexSize = CGSize(width: 25.0, height: 25.0) let padding = CGFloat(30.0) - var parsedMap: ParsedMap { - return Map.parse(content: content) - } - var body: some View { ZStack(alignment: .topLeading) { @@ -50,13 +48,19 @@ struct MapRenderView: View { }.frame( width: mapSize.width, height: mapSize.height + 2 * padding, alignment: .topLeading - ).padding(padding) + ).onAppear { + self.parsedMap = Map.parse(content: content) + }.padding(padding).onChange(of: content) { newState in + self.parsedMap = Map.parse(content: newState) + } } } struct MapRenderView_Previews: PreviewProvider { static var previews: some View { - MapRenderView(content: "", evolution: Stage.stages(.general)).environment( + MapRenderView( + content: Binding.constant(""), evolution: Binding.constant(Stage.stages(.general)) + ).environment( \.managedObjectContext, PersistenceController.preview.container.viewContext) } } diff --git a/Map/Views/MapTextEditor.swift b/Map/Views/MapTextEditor.swift index e030d64..4fbff82 100644 --- a/Map/Views/MapTextEditor.swift +++ b/Map/Views/MapTextEditor.swift @@ -30,6 +30,7 @@ class MapTextEditorController: NSViewController { scrollView.translatesAutoresizingMaskIntoConstraints = false + textView.allowsUndo = true textView.delegate = self textView.textStorage?.delegate = self textView.string = self.text diff --git a/Map/Views/SlowMapRender.swift b/Map/Views/SlowMapRender.swift new file mode 100644 index 0000000..b7cd842 --- /dev/null +++ b/Map/Views/SlowMapRender.swift @@ -0,0 +1,90 @@ +import Cocoa +import SwiftUI + +class SlowMapRenderController: NSViewController { + + var content: String + private var contentBinding: Binding<String> { + Binding( + get: { self.content }, + set: { content in + self.content = content + } + ) + } + + var evolution: Stage + private var evolutionBinding: Binding<Stage> { + Binding( + get: { self.evolution }, + set: { evolution in + self.evolution = evolution + } + ) + } + private var colorSchemeEnvironment: ObservableColorSchemeEnvironment + + private let debouncer: Debouncer = Debouncer(seconds: 0.1) + + init(content: String, evolution: Stage, colorScheme: ColorScheme) { + + self.content = content + self.evolution = evolution + self.colorSchemeEnvironment = ObservableColorSchemeEnvironment(colorScheme: colorScheme) + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + + let renderView = MapRenderView(content: self.contentBinding, evolution: self.evolutionBinding) + .environmentObject(self.colorSchemeEnvironment) + let hostingView = NSHostingView(rootView: renderView) + + self.view = hostingView + } + + func update(content: String, evolution: Stage, colorScheme: ColorScheme) { + self.debouncer.debounce { + DispatchQueue.main.async { + print("Updating: START") + self.content = content + self.evolution = evolution + self.colorSchemeEnvironment.colorScheme = colorScheme + print("Updating: END") + } + } + } + + private class ObservableColorSchemeEnvironment: ObservableObject { + @Published var colorScheme: ColorScheme + + init(colorScheme: ColorScheme) { + self.colorScheme = colorScheme + } + } +} + +struct SlowMapRender: NSViewControllerRepresentable { + + var content: String + var evolution: Stage + let colorScheme: ColorScheme + + func makeNSViewController( + context: NSViewControllerRepresentableContext<SlowMapRender> + ) -> SlowMapRenderController { + return SlowMapRenderController(content: content, evolution: evolution, colorScheme: colorScheme) + } + + func updateNSViewController( + _ nsViewController: SlowMapRenderController, + context: NSViewControllerRepresentableContext<SlowMapRender> + ) { + nsViewController.update( + content: content, evolution: evolution, colorScheme: context.environment.colorScheme) + } +} |