From bead05837b56892cee889d5c5754ceced92fd658 Mon Sep 17 00:00:00 2001 From: Gerrit Linnemann Date: Sun, 4 Oct 2020 14:19:53 +0200 Subject: [PATCH] ObservedObjext --- YAPS/YAPS.xcodeproj/project.pbxproj | 8 ++ YAPS/YAPS/AppDelegate.swift | 18 +++- .../toolbar_folder.imageset/Contents.json | 21 ++++ .../toolbar_folder.imageset/Image.png | Bin 0 -> 3211 bytes YAPS/YAPS/ContentView.swift | 24 +++-- YAPS/YAPS/FinderHelper.swift | 6 +- YAPS/YAPS/Model/ObservableArray.swift | 33 ++++++ YAPS/YAPS/Model/YapsFile.swift | 10 +- YAPS/YAPS/Shared.swift | 16 +-- YAPS/YAPS/Struct/YapsFileCell.swift | 26 +---- YAPS/YAPS/Toolbar.swift | 96 ++++++++++++++++++ 11 files changed, 209 insertions(+), 49 deletions(-) create mode 100644 YAPS/YAPS/Assets.xcassets/toolbar_folder.imageset/Contents.json create mode 100644 YAPS/YAPS/Assets.xcassets/toolbar_folder.imageset/Image.png create mode 100644 YAPS/YAPS/Model/ObservableArray.swift create mode 100644 YAPS/YAPS/Toolbar.swift diff --git a/YAPS/YAPS.xcodeproj/project.pbxproj b/YAPS/YAPS.xcodeproj/project.pbxproj index ad1ae5d..41ba1d6 100644 --- a/YAPS/YAPS.xcodeproj/project.pbxproj +++ b/YAPS/YAPS.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ EC13306F24DD687F008063CF /* YapsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC13306E24DD687F008063CF /* YapsFile.swift */; }; EC13307124DDB3F4008063CF /* FinderHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC13307024DDB3F4008063CF /* FinderHelper.swift */; }; EC13307424DF2B2D008063CF /* YapsFileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC13307324DF2B2D008063CF /* YapsFileCell.swift */; }; + EC62160025160F7100F285FC /* Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6215FF25160F7100F285FC /* Toolbar.swift */; }; + EC6216062517CB8000F285FC /* ObservableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6216052517CB8000F285FC /* ObservableArray.swift */; }; ECF5A75824E070710010A11D /* RawFileExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF5A75724E070710010A11D /* RawFileExtensions.swift */; }; ECF5A76024E41AAC0010A11D /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF5A75F24E41AAC0010A11D /* Shared.swift */; }; ECF5A76224E93C080010A11D /* MiscHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF5A76124E93C080010A11D /* MiscHelper.swift */; }; @@ -32,6 +34,8 @@ EC13306E24DD687F008063CF /* YapsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YapsFile.swift; sourceTree = ""; }; EC13307024DDB3F4008063CF /* FinderHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinderHelper.swift; sourceTree = ""; }; EC13307324DF2B2D008063CF /* YapsFileCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YapsFileCell.swift; sourceTree = ""; }; + EC6215FF25160F7100F285FC /* Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toolbar.swift; sourceTree = ""; }; + EC6216052517CB8000F285FC /* ObservableArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableArray.swift; sourceTree = ""; }; ECF5A75724E070710010A11D /* RawFileExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawFileExtensions.swift; sourceTree = ""; }; ECF5A75F24E41AAC0010A11D /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = ""; }; ECF5A76124E93C080010A11D /* MiscHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiscHelper.swift; sourceTree = ""; }; @@ -79,6 +83,7 @@ EC13307024DDB3F4008063CF /* FinderHelper.swift */, ECF5A75F24E41AAC0010A11D /* Shared.swift */, ECF5A76124E93C080010A11D /* MiscHelper.swift */, + EC6215FF25160F7100F285FC /* Toolbar.swift */, ); path = YAPS; sourceTree = ""; @@ -95,6 +100,7 @@ isa = PBXGroup; children = ( EC13306E24DD687F008063CF /* YapsFile.swift */, + EC6216052517CB8000F285FC /* ObservableArray.swift */, ); path = Model; sourceTree = ""; @@ -179,6 +185,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + EC62160025160F7100F285FC /* Toolbar.swift in Sources */, ECF5A76024E41AAC0010A11D /* Shared.swift in Sources */, EC13305D24DAE92D008063CF /* ContentView.swift in Sources */, EC13307124DDB3F4008063CF /* FinderHelper.swift in Sources */, @@ -186,6 +193,7 @@ EC13306F24DD687F008063CF /* YapsFile.swift in Sources */, EC13307424DF2B2D008063CF /* YapsFileCell.swift in Sources */, EC13305B24DAE92D008063CF /* AppDelegate.swift in Sources */, + EC6216062517CB8000F285FC /* ObservableArray.swift in Sources */, ECF5A76224E93C080010A11D /* MiscHelper.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/YAPS/YAPS/AppDelegate.swift b/YAPS/YAPS/AppDelegate.swift index f4a7add..c315b82 100644 --- a/YAPS/YAPS/AppDelegate.swift +++ b/YAPS/YAPS/AppDelegate.swift @@ -12,6 +12,7 @@ import SwiftUI @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { + @ObservedObject var observedFileList: ObservableArray = Shared.shared.fileList var window: NSWindow! @@ -19,11 +20,15 @@ class AppDelegate: NSObject, NSApplicationDelegate { // Create the SwiftUI view that provides the window contents. let contentView = ContentView() + // Toolbar **needs** a delegate + NSToolbar.yapsToolbar.delegate = self + // Create the window and set the content view. window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false) + window.toolbar = .yapsToolbar window.center() window.setFrameAutosaveName("YAPS Main Window") window.contentView = NSHostingView(rootView: contentView) @@ -49,11 +54,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { */ @IBAction func openFolder(_ sender: Any) { - if !Shared.shared.destinationDefined { - Shared.shared.destination = FinderHelper.shared.selectFolder(modalTitle: "Choose destination folder.") - Shared.shared.destinationDefined = true - } - FinderHelper.shared.iLikeThisImage(yapsFile: Shared.shared.currentFile, destination: Shared.shared.destination) + self.observedFileList.array.removeAll() + self.observedFileList.array.append(contentsOf: FinderHelper.shared.askForFolderAndGetFiles()) } @IBAction func getIt(_ sender: Any) { @@ -76,3 +78,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { //scrollView.magnify(toFit: imageView.frame) } } + +struct AppDelegate_Previews: PreviewProvider { + static var previews: some View { + /*@START_MENU_TOKEN@*/Text("Hello, World!")/*@END_MENU_TOKEN@*/ + } +} diff --git a/YAPS/YAPS/Assets.xcassets/toolbar_folder.imageset/Contents.json b/YAPS/YAPS/Assets.xcassets/toolbar_folder.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/YAPS/YAPS/Assets.xcassets/toolbar_folder.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/YAPS/YAPS/Assets.xcassets/toolbar_folder.imageset/Image.png b/YAPS/YAPS/Assets.xcassets/toolbar_folder.imageset/Image.png new file mode 100644 index 0000000000000000000000000000000000000000..d0e73f7a6ea13b31c42673df5abe7047b864bfe6 GIT binary patch literal 3211 zcmc&%XH-*L7EVIYfKN#tVgO5u(xpjzGzmp|5CJJFO}ca#1A>47gzy9@QjAEk17d)H zh*E-xLX3dGqXdwy2qct9uXE$foB21h=Etn}I7;Ya!3SZLVYPMeQsQXy3Alfv!zBn4Nd=lYigA7PG)-+Vu}) zi&tP4*5jfESm@$-#6lMm!DqsU86)o1v%+k&#v!fvk{)62*@z-A247|#+=Cl^5B3#! z^1gE>Wn}@iC`dekd9QhT%XE z_zlosWcHI9AE^#R*5q|q+HEGz2Fylmq+&H1uAW0!W;NPC)C*gOB{fe(QdoM-A%?!` zLI>5E(I8i=ORzB{HtzCi=@$fK=A|(mu1>GWDuy>CQ-dI0(z|CF$FgM|51x+Ebhz%M zI%Q`5B&RYtIO%Ho=S%XDCcK?GHi3H?M{Wf-6lV0lD2a(<31ks$I!KKZnT#^`ol>72 zJT;6Vm&4>&TD`1(EVJHvL+4>O(RH z$7%LLtMt7$PZeV|CAo*huB#s?K}2J#G7yhEkF%4jBS7>$|H3$)IRA{?$A@{8-bzJG^Py+>&% zdh9?hjWF4()#)qj)YX|?0yK(7EqRF2)9eJgu&vG)dP&W_wxE1fiy$(vV%ZeKyAwBi z;6rq-ASwLLuv&?suvtWJpagVNgRV}jZ{eq)2GrIV3isZ}NM z47L__a_(1OBv=)P-bYkypBsvER~IFPB8iGns;}kGRTy#YaENW%Nh6-%IV>EL<``EF zhB)vUz~Y5Y%y#02YGDR2_dc}W4V*0m0Rc+eUv^KK)^f2yZR`hqK0W)xyOGL`Rcw9> zTjRFVQ866q6MUjSRZ)3&cxYo#qH^AA{c@7yjM})q%YF#qqcTOG> zhieL2q7@DjNp9rzhV1#Sq~LG$tMFF?tq=GJ(+%i; z@NqZ>el|Pnt>D|U`e;4$qmxlSFhTDt0J=q-y{;BwOBsrVHt}b8ZtztZ0uIsUw(zr1 zo&5mmj6HjZdX0i4IB6G?P+^;fl2os)WB949^EDM@U@dOP0!xWXO-;kCDH7d z%7;dr5qc%5xgo|`tUYfx)(?ywS`?4UY}9N_xG^IfSR~!yUlcv<#REa)*dLC6M}hat zae;U>m3l3)?@2u*$K&F0;wJIB7`QHgr4)@&I#X~5PJ*|dNK%l5VqwV`_?#}*^Y&>Y z48SWUNyQZV3xo*cCJCm-hGW-!0096k+Li>pQ-EAscT3UvuxL@h!IjrUX&ZWhTMr#^ z8InY7K|-T2#DpgfhZzL~Y<9j}5gR`9GrxI<9i9R+Dfw`P+0FMb585e7?SYsSeKmhfmzprhsykOuCi-Vt|Db5 zH5bgXeux}Hv@|ASd4sN4POTP?)Vh}m%oGG*T%ZRS!^kA8Df-Y4*;5yHoG`b-g$o0u z_K7Xr;51zD94|jFRcgTB9J}*?6+ws!fU?fb$-DP`WI0V5itzr0 hmtNquT^-vP1D^XFw8i>T-p>6IFgaskK+$uF{X1jaX`BE6 literal 0 HcmV?d00001 diff --git a/YAPS/YAPS/ContentView.swift b/YAPS/YAPS/ContentView.swift index de059cd..5cc3afc 100644 --- a/YAPS/YAPS/ContentView.swift +++ b/YAPS/YAPS/ContentView.swift @@ -9,12 +9,13 @@ import SwiftUI struct ContentView: View { - @State var fileList = [YapsFile]() + @ObservedObject var observedFileList: ObservableArray = Shared.shared.fileList @State var previewImg = Image("placeholder-image") var body: some View { HStack { VStack(alignment: .leading) { + /* HStack { Image.init("folder") @@ -31,9 +32,10 @@ struct ContentView: View { Text("􀈕 Select Folder") } } + */ - List { - ForEach(self.fileList, id: \.self) { yapsFile in + List() { + ForEach(self.observedFileList.array, id: \.self) { yapsFile in YapsFileCell(focused: self.checkCurrentFile(yapsFile: yapsFile), yapsFile: yapsFile) .onTapGesture(perform: { print("pressed \(yapsFile.name)") @@ -46,10 +48,12 @@ struct ContentView: View { .focusable() .onMoveCommand { (direction) in var newIndex = Shared.shared.currentFile.index + print("\(direction)") + switch direction { case MoveCommandDirection.down: - let max = self.fileList.count + let max = self.observedFileList.array.count if (newIndex + 1) < max { newIndex += 1 } @@ -61,7 +65,7 @@ struct ContentView: View { newIndex += 0 } - self.setCurrentFile(newCurrent: self.fileList[newIndex], at: newIndex) + self.setCurrentFile(newCurrent: self.observedFileList.array[newIndex], at: newIndex) } } .frame(width: 260.0) @@ -85,14 +89,14 @@ struct ContentView: View { func setCurrentFile(newCurrent: YapsFile, at: Int) { - let currentFile = self.fileList[at] + let currentFile = self.observedFileList.array[at] Shared.shared.currentFile = currentFile self.resetCurrentFile() - self.fileList[at].current = true + self.observedFileList.array[at].isCurrent = true - self.previewImg = FinderHelper.shared.getImageByURL(source: self.fileList[at].file) + self.previewImg = FinderHelper.shared.getImageByURL(source: self.observedFileList.array[at].file) } func checkCurrentFile(yapsFile: YapsFile) -> Bool { @@ -104,8 +108,8 @@ struct ContentView: View { } func resetCurrentFile() { - for yapsFile in self.fileList { - self.fileList[yapsFile.index].current = false + for yapsFile in self.observedFileList.array { + self.observedFileList.array[yapsFile.index].isCurrent = false } } } diff --git a/YAPS/YAPS/FinderHelper.swift b/YAPS/YAPS/FinderHelper.swift index 60a8b5c..d8022ee 100644 --- a/YAPS/YAPS/FinderHelper.swift +++ b/YAPS/YAPS/FinderHelper.swift @@ -53,7 +53,7 @@ class FinderHelper { var index = 0 for item in items { - let yapsFile: YapsFile = YapsFile(index: -1, name: item.lastPathComponent, file: item, current: false) + let yapsFile: YapsFile = YapsFile(index: -1, name: item.lastPathComponent, file: item, isCurrent: false) fileList.append(yapsFile) index += 1 } @@ -61,6 +61,8 @@ class FinderHelper { // failed to read directory – bad permissions, perhaps? } + print("Found \(fileList.count) file\(fileList.count != 1 ? "s" : "") to show") + return indexify(list: fileList.sorted { let f01: YapsFile = $0 let f02: YapsFile = $1 @@ -77,7 +79,7 @@ class FinderHelper { var index = 0 for item in list { - let yapsFile: YapsFile = YapsFile(index: index, name: item.file.lastPathComponent, file: item.file, current: false) + let yapsFile: YapsFile = YapsFile(index: index, name: item.file.lastPathComponent, file: item.file, isCurrent: false) indexifiedFileList.append(yapsFile) index += 1 } diff --git a/YAPS/YAPS/Model/ObservableArray.swift b/YAPS/YAPS/Model/ObservableArray.swift new file mode 100644 index 0000000..febb70d --- /dev/null +++ b/YAPS/YAPS/Model/ObservableArray.swift @@ -0,0 +1,33 @@ +// +// ObservableArray.swift +// YAPS +// +// Created by Gerrit Linnemann on 20.09.20. +// Copyright © 2020 Adawim UG (haftungsbeschränkt). All rights reserved. +// + +import Foundation +import Combine +import SwiftUI + +class ObservableArray: ObservableObject { + + @Published var array:[T] = [] + var cancellables = [AnyCancellable]() + + init(array: [T]) { + self.array = array + } + + func observeChildrenChanges() -> ObservableArray { + let array2 = array as! [T] + array2.forEach({ + let c = $0.objectWillChange.sink(receiveValue: { _ in self.objectWillChange.send() }) + + // Important: You have to keep the returned value allocated, + // otherwise the sink subscription gets cancelled + self.cancellables.append(c) + }) + return self as! ObservableArray + } +} diff --git a/YAPS/YAPS/Model/YapsFile.swift b/YAPS/YAPS/Model/YapsFile.swift index a2c53a0..010b646 100644 --- a/YAPS/YAPS/Model/YapsFile.swift +++ b/YAPS/YAPS/Model/YapsFile.swift @@ -9,10 +9,18 @@ import Foundation struct YapsFile: Identifiable, Hashable { + let id = UUID() var index: Int var name: String var file: URL - var current: Bool + var isCurrent: Bool + + init(index: Int, name: String, file: URL, isCurrent: Bool) { + self.index = index + self.name = name + self.file = file + self.isCurrent = isCurrent + } } diff --git a/YAPS/YAPS/Shared.swift b/YAPS/YAPS/Shared.swift index 86a9d76..22cf4b4 100644 --- a/YAPS/YAPS/Shared.swift +++ b/YAPS/YAPS/Shared.swift @@ -12,11 +12,15 @@ import SwiftUI class Shared: ObservableObject { static let shared = Shared() - @Published var fileList = [YapsFile]() - @Published var currentFile: YapsFile = YapsFile(index: -1, name: "EMPTY", file: .init(fileURLWithPath: "Y"), current: false) + var fileList: ObservableArray + var currentFile: YapsFile + var destination: URL + var destinationDefined: Bool - var destination: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] - var destinationDefined: Bool = false - - private init() { } + init() { + self.fileList = ObservableArray(array: []) + self.currentFile = YapsFile(index: -1, name: "EMPTY", file: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0], isCurrent: false) + self.destination = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + self.destinationDefined = false + } } diff --git a/YAPS/YAPS/Struct/YapsFileCell.swift b/YAPS/YAPS/Struct/YapsFileCell.swift index d37e920..5d912aa 100644 --- a/YAPS/YAPS/Struct/YapsFileCell.swift +++ b/YAPS/YAPS/Struct/YapsFileCell.swift @@ -11,8 +11,7 @@ import SwiftUI struct YapsFileCell: View { @State var focused: Bool - - var yapsFile : YapsFile + @State var yapsFile : YapsFile var body: some View { Group { @@ -42,26 +41,3 @@ struct YapsFileCell: View { } } } - -struct YapsFileCell_Previews: PreviewProvider { - static var previews: some View { - Group { - VStack(alignment: .leading) { - // Dateiname - Text("dateiname.xyz") - .foregroundColor(.primary) - .multilineTextAlignment(.leading) - .lineLimit(1) - .aspectRatio(contentMode: .fill) - - // Details - Text("xyz") - .font(.footnote) - .foregroundColor(.secondary) - .multilineTextAlignment(.leading) - .lineLimit(1) - .aspectRatio(contentMode: .fill) - } - } - } -} diff --git a/YAPS/YAPS/Toolbar.swift b/YAPS/YAPS/Toolbar.swift new file mode 100644 index 0000000..3ffcf64 --- /dev/null +++ b/YAPS/YAPS/Toolbar.swift @@ -0,0 +1,96 @@ +// +// Toolbar.swift +// SUIToolbarPlay +// +// Created by Bill So on 4/23/20. +// Copyright © 2020 Bill So. All rights reserved. +// + +import AppKit + +extension NSImage.Name { + static let folder = "toolbar_folder" +} + +extension NSToolbarItem.Identifier { + static let calendar = NSToolbarItem.Identifier(rawValue: "ShowCalendar") + static let today = NSToolbarItem.Identifier(rawValue: "GoToToday") +} + +extension NSToolbar { + static let yapsToolbar: NSToolbar = { + let toolbar = NSToolbar(identifier: "YapsToolbar") + toolbar.displayMode = .iconOnly + + return toolbar + }() +} + +extension AppDelegate: NSToolbarDelegate { + func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { + [.today, .calendar] + } + + func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { + [.today, .calendar] + } + + @objc + func toolbarOpenSourceAction() { + self.observedFileList.array.removeAll() + self.observedFileList.array.append(contentsOf: FinderHelper.shared.askForFolderAndGetFiles()) + } + +func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { + switch itemIdentifier { + case NSToolbarItem.Identifier.calendar: + let button = NSButton(image: NSImage(named: .folder)!, target: nil, action: #selector(toolbarOpenSourceAction)) + button.bezelStyle = .texturedRounded + return customToolbarItem(itemIdentifier: .calendar, label: "Open", paletteLabel: "Open", toolTip: "Open source folder", itemContent: button) + default: + return nil + } + } + + /** + Mostly base on Apple sample code: https://developer.apple.com/documentation/appkit/touch_bar/integrating_a_toolbar_and_touch_bar_into_your_app + */ + func customToolbarItem( + itemIdentifier: NSToolbarItem.Identifier, + label: String, + paletteLabel: String, + toolTip: String, + itemContent: NSButton) -> NSToolbarItem? { + + let toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier) + + toolbarItem.label = label + toolbarItem.paletteLabel = paletteLabel + toolbarItem.toolTip = toolTip + /** + You don't need to set a `target` if you know what you are doing. + + In this example, AppDelegate is also the toolbar delegate. + + Since AppDelegate is not a responder, implementing an IBAction in the AppDelegate class has no effect. Try using a subclass of NSWindow or NSWindowController to implement your action methods and use them as the toolbar delegate instead. + + Ref: https://developer.apple.com/documentation/appkit/nstoolbaritem/1525982-target + + From doc: + + If target is nil, the toolbar will call action and attempt to invoke the action on the first responder and, failing that, pass the action up the responder chain. + */ + //toolbarItem.target = itemContent.target + //toolbarItem.action = itemContent.action + + toolbarItem.view = itemContent + + // We actually need an NSMenuItem here, so we construct one. + let menuItem: NSMenuItem = NSMenuItem() + menuItem.submenu = nil + menuItem.title = label + toolbarItem.menuFormRepresentation = menuItem + + return toolbarItem + } +}