diff options
| author | Ruben Beltran del Rio <ruben@unlimited.pizza> | 2021-02-02 09:16:46 +0100 |
|---|---|---|
| committer | Ruben Beltran del Rio <ruben@unlimited.pizza> | 2021-02-02 09:16:46 +0100 |
| commit | 1b85f723b48d38cf345bb9a4f3fd01aa6039b50b (patch) | |
| tree | b4795a83350b2bdd800e4d2749d1327c17bc385f /Map | |
Initial commit
Diffstat (limited to 'Map')
| -rw-r--r-- | Map/Assets.xcassets/AccentColor.colorset/Contents.json | 11 | ||||
| -rw-r--r-- | Map/Assets.xcassets/AppIcon.appiconset/Contents.json | 58 | ||||
| -rw-r--r-- | Map/Assets.xcassets/Contents.json | 6 | ||||
| -rw-r--r-- | Map/ContentView.swift | 97 | ||||
| -rw-r--r-- | Map/Extensions/Binding+unwrap.swift | 12 | ||||
| -rw-r--r-- | Map/Extensions/Date+format.swift | 17 | ||||
| -rw-r--r-- | Map/Extensions/Map+parse.swift | 106 | ||||
| -rw-r--r-- | Map/Info.plist | 24 | ||||
| -rw-r--r-- | Map/Map.entitlements | 10 | ||||
| -rw-r--r-- | Map/Map.xcdatamodeld/.xccurrentversion | 8 | ||||
| -rw-r--r-- | Map/Map.xcdatamodeld/Map.xcdatamodel/contents | 12 | ||||
| -rw-r--r-- | Map/MapApp.swift | 20 | ||||
| -rw-r--r-- | Map/MapDetail.swift | 44 | ||||
| -rw-r--r-- | Map/MapRender.swift | 125 | ||||
| -rw-r--r-- | Map/Persistence.swift | 58 | ||||
| -rw-r--r-- | Map/Preview Content/Preview Assets.xcassets/Contents.json | 6 | ||||
| -rw-r--r-- | Map/Stage.swift | 108 |
17 files changed, 722 insertions, 0 deletions
diff --git a/Map/Assets.xcassets/AccentColor.colorset/Contents.json b/Map/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Map/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Map/Assets.xcassets/AppIcon.appiconset/Contents.json b/Map/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..3f00db4 --- /dev/null +++ b/Map/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Map/Assets.xcassets/Contents.json b/Map/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Map/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Map/ContentView.swift b/Map/ContentView.swift new file mode 100644 index 0000000..ae5b6cd --- /dev/null +++ b/Map/ContentView.swift @@ -0,0 +1,97 @@ +// +// ContentView.swift +// Map +// +// Created by Ruben Beltran del Rio on 2/1/21. +// + +import SwiftUI +import CoreData + +struct ContentView: View { + @Environment(\.managedObjectContext) private var viewContext + + @FetchRequest( + sortDescriptors: [NSSortDescriptor(keyPath: \Map.createdAt, ascending: true)], + animation: .default) + private var maps: FetchedResults<Map> + + var body: some View { + NavigationView { + List { + ForEach(maps) { map in + NavigationLink(destination: MapDetailView(map: map)) { + HStack { + Text(map.title ?? "Untitled Map") + Text(map.createdAt!.format()) + .font(.footnote) + .foregroundColor(Color.accentColor) + } + } + } + .onDelete(perform: deleteMaps) + } + .toolbar { + HStack { + Button(action: toggleSidebar) { + Label("Toggle Sidebar", systemImage: "sidebar.left") + } + Button(action: addMap) { + Label("Add Map", systemImage: "plus") + } + } + } + } + } + + private func toggleSidebar() { + NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil) + } + + private func addMap() { + withAnimation { + let newMap = Map(context: viewContext) + newMap.uuid = UUID() + newMap.createdAt = Date() + newMap.title = "Map \(newMap.createdAt!.format())" + newMap.content = "" + + do { + try viewContext.save() + } catch { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + let nsError = error as NSError + fatalError("Unresolved error \(nsError), \(nsError.userInfo)") + } + } + } + + private func deleteMaps(offsets: IndexSet) { + withAnimation { + offsets.map { maps[$0] }.forEach(viewContext.delete) + + do { + try viewContext.save() + } catch { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + let nsError = error as NSError + fatalError("Unresolved error \(nsError), \(nsError.userInfo)") + } + } + } +} + +private let mapFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .medium + return formatter +}() + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) + } +} diff --git a/Map/Extensions/Binding+unwrap.swift b/Map/Extensions/Binding+unwrap.swift new file mode 100644 index 0000000..3498cc3 --- /dev/null +++ b/Map/Extensions/Binding+unwrap.swift @@ -0,0 +1,12 @@ +import SwiftUI + +extension Binding { + init(_ source: Binding<Value?>, _ defaultValue: Value) { + // Ensure a non-nil value in `source`. + if source.wrappedValue == nil { + source.wrappedValue = defaultValue + } + // Unsafe unwrap because *we* know it's non-nil now. + self.init(source)! + } +} diff --git a/Map/Extensions/Date+format.swift b/Map/Extensions/Date+format.swift new file mode 100644 index 0000000..c12e67d --- /dev/null +++ b/Map/Extensions/Date+format.swift @@ -0,0 +1,17 @@ +// +// Date+format.swift +// Map +// +// Created by Ruben Beltran del Rio on 2/1/21. +// + +import Foundation + +extension Date { + func format() -> String { + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .medium + return formatter.string(from: self) + } +} diff --git a/Map/Extensions/Map+parse.swift b/Map/Extensions/Map+parse.swift new file mode 100644 index 0000000..03e7993 --- /dev/null +++ b/Map/Extensions/Map+parse.swift @@ -0,0 +1,106 @@ +import CoreGraphics +import Foundation + +let VERTEX_PATTERN = "([^\\(]+?)[\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)" +let EDGE_PATTERN = "(.+?)[\\s]*->[\\s]*(.+)" + +struct ParsedMap { + let vertices: [Vertex] + let edges: [MapEdge] +} + +struct Vertex { + let id: Int + let label: String + let position: CGPoint +} + +struct MapEdge { + let id: Int + let origin: CGPoint + let destination: CGPoint +} + +// Extracts the vertices from the text + +func parseVertices(_ text: String) -> [String: CGPoint] { + + var result: [String: CGPoint] = [:] + let regex = try! NSRegularExpression(pattern: VERTEX_PATTERN, options: .caseInsensitive) + + let lines = text.split(whereSeparator: \.isNewline) + + for line in lines { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + + if matches.count > 0 && matches[0].numberOfRanges == 4{ + + let match = matches[0]; + let key = String(line[Range(match.range(at: 1), in: line)!]) + let xString = String(line[Range(match.range(at: 2), in: line)!]) + let yString = String(line[Range(match.range(at: 3), in: line)!]) + let x = CGFloat(truncating: NumberFormatter().number(from:xString) ?? 0.0) + let y = CGFloat(truncating: NumberFormatter().number(from:yString) ?? 0.0) + let point = CGPoint(x: x, y: y) + + result[key] = point + } + } + + return result +} + +// Extracts the edges from the text + +func parseEdges(_ text: String, vertices: [String: CGPoint]) -> [MapEdge] { + + var result: [MapEdge] = [] + let regex = try! NSRegularExpression(pattern: EDGE_PATTERN, options: .caseInsensitive) + + let lines = text.split(whereSeparator: \.isNewline) + + for (index, line) in lines.enumerated() { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + + if matches.count > 0 && matches[0].numberOfRanges == 3 { + + let match = matches[0]; + let vertexA = String(line[Range(match.range(at: 1), in: line)!]) + let vertexB = String(line[Range(match.range(at: 2), in: line)!]) + + if let origin = vertices[vertexA] { + if let destination = vertices[vertexB] { + result.append(MapEdge(id: index, origin: origin, destination: destination)) + } + } + } + } + + return result +} + +// Converts vetex dictionary to array + +func mapVertices(_ vertices: [String: CGPoint]) -> [Vertex] { + var i = 0 + return vertices.map { label, position in + i += 1; + return Vertex(id: i, label: label, position: position) + } +} + +extension Map { + func parse() -> ParsedMap { + + let text = self.content ?? "" + let vertices = parseVertices(text) + let mappedVertices = mapVertices(vertices) + let edges = parseEdges(text, vertices: vertices) + print(mappedVertices) + print(edges) + + return ParsedMap(vertices: mappedVertices, edges: edges) + } +} diff --git a/Map/Info.plist b/Map/Info.plist new file mode 100644 index 0000000..69c84ae --- /dev/null +++ b/Map/Info.plist @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>$(DEVELOPMENT_LANGUAGE)</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundlePackageType</key> + <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleVersion</key> + <string>1</string> + <key>LSMinimumSystemVersion</key> + <string>$(MACOSX_DEPLOYMENT_TARGET)</string> +</dict> +</plist> diff --git a/Map/Map.entitlements b/Map/Map.entitlements new file mode 100644 index 0000000..f2ef3ae --- /dev/null +++ b/Map/Map.entitlements @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.security.app-sandbox</key> + <true/> + <key>com.apple.security.files.user-selected.read-only</key> + <true/> +</dict> +</plist> diff --git a/Map/Map.xcdatamodeld/.xccurrentversion b/Map/Map.xcdatamodeld/.xccurrentversion new file mode 100644 index 0000000..ee72e60 --- /dev/null +++ b/Map/Map.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>_XCCurrentVersionName</key> + <string>Map.xcdatamodel</string> +</dict> +</plist> diff --git a/Map/Map.xcdatamodeld/Map.xcdatamodel/contents b/Map/Map.xcdatamodeld/Map.xcdatamodel/contents new file mode 100644 index 0000000..ea8b276 --- /dev/null +++ b/Map/Map.xcdatamodeld/Map.xcdatamodel/contents @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D5029f" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier=""> + <entity name="Map" representedClassName="Map" syncable="YES" codeGenerationType="class"> + <attribute name="content" optional="YES" attributeType="String"/> + <attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/> + <attribute name="title" optional="YES" attributeType="String"/> + <attribute name="uuid" optional="YES" attributeType="UUID" usesScalarValueType="NO"/> + </entity> + <elements> + <element name="Map" positionX="-63" positionY="-18" width="128" height="89"/> + </elements> +</model>
\ No newline at end of file diff --git a/Map/MapApp.swift b/Map/MapApp.swift new file mode 100644 index 0000000..10b6bcb --- /dev/null +++ b/Map/MapApp.swift @@ -0,0 +1,20 @@ +// +// MapApp.swift +// Map +// +// Created by Ruben Beltran del Rio on 2/1/21. +// + +import SwiftUI + +@main +struct MapApp: App { + let persistenceController = PersistenceController.shared + + var body: some Scene { + WindowGroup { + ContentView() + .environment(\.managedObjectContext, persistenceController.container.viewContext) + } + } +} diff --git a/Map/MapDetail.swift b/Map/MapDetail.swift new file mode 100644 index 0000000..74e3c8d --- /dev/null +++ b/Map/MapDetail.swift @@ -0,0 +1,44 @@ +// +// ContentView.swift +// Map +// +// Created by Ruben Beltran del Rio on 2/1/21. +// + +import SwiftUI +import CoreData + +struct MapDetailView: View { + @Environment(\.managedObjectContext) private var viewContext + + @ObservedObject var map: Map + + @State private var selectedEvolution = StageType.General + + var body: some View { + VSplitView { + VStack { + TextField("Title", text: Binding($map.title, ""), onCommit: { + try? viewContext.save() + }) + Picker("Evolution", selection: $selectedEvolution) { + ForEach(StageType.allCases) { stage in + Text(Stage.title(stage)).tag(stage) + } + } + TextEditor(text: Binding($map.content, "")).onChange(of: map.content) { _ in + try? viewContext.save() + }.font(Font.system(size: 16, design: .monospaced)) + } + ScrollView([.horizontal, .vertical]) { + MapRenderView(map: map, evolution: Stage.stages(selectedEvolution)) + } + } + } +} + +struct MapDetailView_Previews: PreviewProvider { + static var previews: some View { + MapDetailView(map: Map()).environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) + } +} diff --git a/Map/MapRender.swift b/Map/MapRender.swift new file mode 100644 index 0000000..79d80b5 --- /dev/null +++ b/Map/MapRender.swift @@ -0,0 +1,125 @@ +// +// ContentView.swift +// Map +// +// Created by Ruben Beltran del Rio on 2/1/21. +// + +import SwiftUI +import CoreData +import CoreGraphics + +struct MapRenderView: View { + @ObservedObject var map: Map + let evolution: Stage + + let VERTEX_SIZE = CGSize(width: 25.0, height: 25.0) + let ARROWHEAD_SIZE = CGFloat(10.0) + let LINE_WIDTH = CGFloat(1.0) + let PADDING = CGFloat(4.0) + let STAGE_FRAME = CGSize(width: 245, height: 50) + + var parsedMap: ParsedMap { + return map.parse() + } + + var body: some View { + ZStack(alignment: .topLeading) { + ZStack (alignment: .topLeading) { + // The Axes + Path { path in + path.move(to: CGPoint(x: 0, y: 0)) + path.addLine(to: CGPoint(x: 0, y: 1000)) + path.addLine(to: CGPoint(x: 1000, y: 1000)) + path.move(to: CGPoint(x: 1000, y: 1000)) + path.closeSubpath() + + }.strokedPath(StrokeStyle(lineWidth: LINE_WIDTH * 2)) + Text("Visible").font(.title3).rotationEffect(Angle(degrees: -90.0)).offset(CGSize(width: -35.0, height: 0.0)) + Text("Value Chain").font(.title).rotationEffect(Angle(degrees: -90.0)).offset(CGSize(width: -72.0, height: 480.0)) + Text("Invisible").font(.title3).rotationEffect(Angle(degrees: -90.0)).offset(CGSize(width: -40.0, height: 980.0)) + Text(evolution.I).font(.title3).frame(width: STAGE_FRAME.width, height: STAGE_FRAME.height, alignment: .topLeading).offset(CGSize(width: 0.0, height: 1000.0)) + Text(evolution.II).font(.title3).frame(width: STAGE_FRAME.width, height: STAGE_FRAME.height, alignment: .topLeading).offset(CGSize(width: 250.0, height: 1000.0)) + Text(evolution.III).font(.title3).frame(width: STAGE_FRAME.width, height: STAGE_FRAME.height, alignment: .topLeading).offset(CGSize(width: 500.0, height: 1000.0)) + Text(evolution.IV).font(.title3).frame(width: STAGE_FRAME.width, height: STAGE_FRAME.height, alignment: .topLeading).offset(CGSize(width: 750.0, height: 1000.0)) + }.offset(CGSize(width: 0.0, height: 0.0)) + + // The Lanes + Path { path in + path.move(to: CGPoint(x: 250, y: 0)) + path.addLine(to: CGPoint(x: 250, y: 1000)) + path.move(to: CGPoint(x: 500, y: 0)) + path.addLine(to: CGPoint(x: 500, y: 1000)) + path.move(to: CGPoint(x: 750, y: 0)) + path.addLine(to: CGPoint(x: 750, y: 1000)) + path.move(to: CGPoint(x: 250, y: 0)) + path.closeSubpath() + }.strokedPath(StrokeStyle(lineWidth: LINE_WIDTH, dash: [10.0])) + + Path { path in + path.addRect(CGRect(x: 0, y: 0, width: 250, height: 1000)) + }.fill(Color.red).opacity(0.1) + Path { path in + path.addRect(CGRect(x: 250, y: 0, width: 250, height: 1000)) + }.fill(Color.orange).opacity(0.1) + Path { path in + path.addRect(CGRect(x: 500, y: 0, width: 250, height: 1000)) + }.fill(Color.yellow).opacity(0.1) + Path { path in + path.addRect(CGRect(x: 750, y: 0, width: 250, height: 1000)) + }.fill(Color.green).opacity(0.1) + + // The Vertices + + ForEach(parsedMap.vertices, id: \.id) { vertex in + Path { path in + path.addEllipse(in: CGRect( + origin: vertex.position, size: VERTEX_SIZE + )) + } + Text(vertex.label).foregroundColor(Color.gray).offset(CGSize( + width: vertex.position.x + VERTEX_SIZE.width + PADDING, + height: vertex.position.y)) + } + + // The Edges + + ForEach(parsedMap.edges, id: \.id) { edge in + Path { path in + + let slope = (edge.destination.y - edge.origin.y) / (edge.destination.x - edge.origin.x) + let angle = atan(slope) + let multiplier = CGFloat(slope < 0 ? -1.0 : 1.0) + let upperAngle = angle - CGFloat.pi / 4.0 + let lowerAngle = angle + CGFloat.pi / 4.0 + + let offsetOrigin = CGPoint(x: edge.origin.x + multiplier * (VERTEX_SIZE.width / 2.0) * cos(angle), y: edge.origin.y + multiplier * (VERTEX_SIZE.height / 2.0) * sin(angle)) + let offsetDestination = CGPoint(x: edge.destination.x - multiplier * (VERTEX_SIZE.width / 2.0) * cos(angle), y: edge.destination.y - multiplier * (VERTEX_SIZE.height / 2.0) * sin(angle)) + + path.move(to: offsetOrigin) + path.addLine(to: offsetDestination) + + // Arrowheads + path.move(to: offsetDestination) + path.addLine(to: CGPoint(x: offsetDestination.x - multiplier * ARROWHEAD_SIZE * cos(upperAngle), y: + offsetDestination.y - multiplier * ARROWHEAD_SIZE * sin(upperAngle))) + + path.move(to: offsetDestination) + path.addLine(to: CGPoint(x: offsetDestination.x - multiplier * ARROWHEAD_SIZE * cos(lowerAngle), y: + offsetDestination.y - multiplier * ARROWHEAD_SIZE * sin(lowerAngle))) + path.move(to: offsetDestination) + path.closeSubpath() + }.applying(CGAffineTransform(translationX: VERTEX_SIZE.width / 2.0, y: VERTEX_SIZE.height / 2.0)).strokedPath(StrokeStyle(lineWidth: LINE_WIDTH)) + } + }.frame( + minWidth: 1050.0, maxWidth: .infinity, + minHeight: 1050.0, maxHeight: .infinity, alignment: .topLeading + ).padding(30.0) + } +} + +struct MapRenderView_Previews: PreviewProvider { + static var previews: some View { + MapDetailView(map: Map()).environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) + } +} diff --git a/Map/Persistence.swift b/Map/Persistence.swift new file mode 100644 index 0000000..49bb2d9 --- /dev/null +++ b/Map/Persistence.swift @@ -0,0 +1,58 @@ +// +// Persistence.swift +// Map +// +// Created by Ruben Beltran del Rio on 2/1/21. +// + +import CoreData + +struct PersistenceController { + static let shared = PersistenceController() + + static var preview: PersistenceController = { + let result = PersistenceController(inMemory: true) + let viewContext = result.container.viewContext + for _ in 0..<10 { + let newMap = Map(context: viewContext) + newMap.uuid = UUID() + newMap.createdAt = Date() + newMap.title = "Map \(newMap.createdAt!.format())" + newMap.content = "" + } + do { + try viewContext.save() + } catch { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + let nsError = error as NSError + fatalError("Unresolved error \(nsError), \(nsError.userInfo)") + } + return result + }() + + let container: NSPersistentCloudKitContainer + + init(inMemory: Bool = false) { + container = NSPersistentCloudKitContainer(name: "Map") + if inMemory { + container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") + } + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error as NSError? { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + + /* + Typical reasons for an error here include: + * The parent directory does not exist, cannot be created, or disallows writing. + * The persistent store is not accessible, due to permissions or data protection when the device is locked. + * The device is out of space. + * The store could not be migrated to the current model version. + Check the error message to determine what the actual problem was. + */ + fatalError("Unresolved error \(error), \(error.userInfo)") + } + }) + } +} diff --git a/Map/Preview Content/Preview Assets.xcassets/Contents.json b/Map/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Map/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Map/Stage.swift b/Map/Stage.swift new file mode 100644 index 0000000..4a53ada --- /dev/null +++ b/Map/Stage.swift @@ -0,0 +1,108 @@ +struct Stage { + let I: String + let II: String + let III: String + let IV: String + + static func stages(_ type: StageType) -> Stage { + switch(type) { + case .General: + return Stage(I: "Genesis", II: "Custom Built", III: "Product (+rental)", IV: "Commodity (+utility)") + case .Ubiquity: + return Stage(I: "Rare", II: "Slowly Increasing Consumption", III: "Rapidly Increasing Consumption", IV: "Widespread and stabilising") + case .Certainty: + return Stage(I: "Poorly Understood", II: "Rapid Increase In Learning", III: "Rapid Increase in Use / fit for purpose", IV: "Commonly understood (in terms of use)") + case .PublicationTypes: + return Stage(I: "Normally describing the wonder of the thing", II: "Build / construct / awareness and learning", III: "Maintenance / operations / installation / feature", IV: "Focused on use") + case .Market: + return Stage(I: "Undefined Market", II: "Forming Market", III: "Growing Market", IV: "Mature Market") + case .KnowledgeManagement: + return Stage(I: "Uncertain", II: "Learning on use", III: "Learning on operation", IV: "Known / accepted") + case .MarketPerception: + return Stage(I: "Chaotic (non-linear)", II: "Domain of experts", III: "Increasing expectation of use", IV: "Ordered (appearance of being trivial) / trivial") + case .UserPerception: + return Stage(I: "Different / confusing / exciting / surprising", II: "Leading edge / emerging", III: "Increasingly common / disappointed if not used", IV: "Standard / expected") + case .PerceptionInIndustry: + return Stage(I: "Competitive advantage / unpredictable / unknown", II: "Competitive advantage / ROI / case examples", III: "Advantage through implementation / features", IV: "Cost of doing business") + case .FocusOfValue: + return Stage(I: "High future worth", II: "Seeking profit / ROI", III: "High profitability", IV: "High volume / reducing margin") + case .Understanding: + return Stage(I: "Poorly Understood / unpredictable", II: "Increasing understanding / development of measures", III: "Increasing education / constant refinement of needs / measures", IV: "Believed to be well defined / stable / measurable") + case .Comparison: + return Stage(I: "Constantly changing / a differential / unstable", II: "Learning from others / testing the water / some evidential support", III: "Feature difference", IV: "Essential / operational advantage") + case .Failure: + return Stage(I: "High / tolerated / assumed", II: "Moderate / unsurprising but disappointed", III: "Not tolerated, focus on constant improvement", IV: "Operational efficiency and surprised by failure") + case .MarketAction: + return Stage(I: "Gambling / driven by gut", II: "Exploring a \"found\" value", III: "Market analysis / listening to customers", IV: "Metric driven / build what is needed") + case .Efficiency: + return Stage(I: "Reducing the cost of change (experimentation)", II: "Reducing cost of waste (Learning)", III: "Reducing cost of waste (Learning)", IV: "Reducing cost of deviation (Volume)") + case .DecisionDrivers: + return Stage(I: "Heritage / culture", II: "Analyses & synthesis", III: "Analyses & synthesis", IV: "Previous Experience") + case .Behavior: + return Stage(I: "Needs to be pushed", II: "Shown in simple situations", III: "Shown in moderately complex situations ", IV: "Always shown") + } + } + + static func title(_ type: StageType) -> String { + switch(type) { + case .General: + return "General" + case .Ubiquity: + return "Ubiquity" + case .Certainty: + return "Certainty" + case .PublicationTypes: + return "Publication Types" + case .Market: + return "Market" + case .KnowledgeManagement: + return "Knowledge Management" + case .MarketPerception: + return "Market Perception" + case .UserPerception: + return "User Perception" + case .PerceptionInIndustry: + return "Perception In Industry" + case .FocusOfValue: + return "Focus Of Value" + case .Understanding: + return "Understanding" + case .Comparison: + return "Comparison" + case .Failure: + return "Failure" + case .MarketAction: + return "Market Action" + case .Efficiency: + return "Efficiency" + case .DecisionDrivers: + return "Decision Drivers" + case .Behavior: + return "Behavior" + } + } +} + +enum StageType: String, CaseIterable, Identifiable { + case General + case Ubiquity + case Certainty + case PublicationTypes + case Market + case KnowledgeManagement + case MarketPerception + case UserPerception + case PerceptionInIndustry + case FocusOfValue + case Understanding + case Comparison + case Failure + case MarketAction + case Efficiency + case DecisionDrivers + case Behavior + + var id: String { self.rawValue } +} + + |