Created
August 21, 2019 13:25
-
-
Save PaulWoodIII/5440a5ef78bd48d246d40616b1a3bdc9 to your computer and use it in GitHub Desktop.
Insertion Sort Visualized on the Apple Watch using SwiftUI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // ContentView.swift | |
| // WatchSort WatchKit Extension | |
| // | |
| // Created by Paul Wood on 8/21/19. | |
| // Copyright © 2019 Paul Wood. All rights reserved. | |
| // | |
| // InsertionSortArray take from Apple's Sample Code and modified for SwiftUI | |
| // https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/using_collection_view_compositional_layouts_and_diffable_data_sources | |
| // Check out the talk on diffable datasources here: https://developer.apple.com/videos/play/wwdc2019/220 | |
| import SwiftUI | |
| import Combine | |
| public class InsertionSortArray: Hashable, Identifiable, ObservableObject, CustomStringConvertible { | |
| public var description: String { | |
| let allNodeDescription = self.nodes.map { (node) -> String in | |
| return node.description | |
| } | |
| return allNodeDescription.joined(separator: ",") | |
| } | |
| public struct SortNode: Hashable, Identifiable, CustomStringConvertible { | |
| public var description: String { | |
| return String(value) | |
| } | |
| let value: Int | |
| let color: UIColor | |
| init(value: Int, maxValue: Int) { | |
| self.value = value | |
| let hue = CGFloat(value) / CGFloat(maxValue) | |
| self.color = UIColor(hue: hue, saturation: 1.0, brightness: 1.0, alpha: 1.0) | |
| } | |
| private let identifier = UUID() | |
| public var id: UUID { | |
| return identifier | |
| } | |
| public func hash(into hasher: inout Hasher) { | |
| hasher.combine(identifier) | |
| } | |
| public static func == (lhs: SortNode, rhs: SortNode) -> Bool { | |
| return lhs.identifier == rhs.identifier | |
| } | |
| } | |
| var values: [SortNode] { | |
| return nodes | |
| } | |
| var isSorted: Bool { | |
| return isSortedInternal | |
| } | |
| func sortNext() { | |
| objectWillChange.send() | |
| performNextSortStep() | |
| } | |
| init(count: Int) { | |
| nodes = (0..<count).map { SortNode(value: $0, maxValue: count) }.shuffled() | |
| } | |
| func randomize() { | |
| objectWillChange.send() | |
| self.isSortedInternal = false | |
| currentIndex = 1 | |
| nodes = nodes.shuffled() | |
| } | |
| public func hash(into hasher: inout Hasher) { | |
| hasher.combine(identifier) | |
| } | |
| public static func == (lhs: InsertionSortArray, rhs: InsertionSortArray) -> Bool { | |
| return lhs.identifier == rhs.identifier | |
| } | |
| private var identifier = UUID() | |
| private var currentIndex = 1 | |
| private var isSortedInternal = false | |
| private var nodes: [SortNode] | |
| public var objectWillChange = PassthroughSubject<Void, Never>() | |
| } | |
| extension InsertionSortArray { | |
| fileprivate func performNextSortStep() { | |
| if isSortedInternal { | |
| return | |
| } | |
| if nodes.count == 1 { | |
| isSortedInternal = true | |
| return | |
| } | |
| var index = currentIndex | |
| let currentNode = nodes[index] | |
| index -= 1 | |
| while index >= 0 && currentNode.value < nodes[index].value { | |
| let tmp = nodes[index] | |
| nodes[index] = currentNode | |
| nodes[index + 1] = tmp | |
| index -= 1 | |
| } | |
| currentIndex += 1 | |
| if currentIndex >= nodes.count { | |
| isSortedInternal = true | |
| } | |
| } | |
| } | |
| class Pulse: ObservableObject { | |
| var shouldEmmit: CurrentValueSubject<Bool, Never> | |
| public var publisher: AnyPublisher<Void, Never> | |
| init() { | |
| let shouldEmmit = CurrentValueSubject<Bool, Never>(false) | |
| let publisher = Timer.publish(every: 0.5, on: RunLoop.main, in: .default) | |
| .autoconnect() | |
| .filter({ _ in shouldEmmit.value }) | |
| .map { _ -> Void in } | |
| .eraseToAnyPublisher() | |
| self.shouldEmmit = shouldEmmit | |
| self.publisher = publisher | |
| } | |
| public func toggle() { | |
| self.shouldEmmit.send(!self.shouldEmmit.value) | |
| } | |
| } | |
| struct InsertionSortView: View { | |
| private static let columnCount = 16 | |
| @ObservedObject var list = InsertionSortArray(count: columnCount) | |
| @ObservedObject var pulse = Pulse() | |
| private func handleTap() { | |
| if !self.list.isSorted { | |
| self.list.sortNext() | |
| } else { | |
| self.list.randomize() | |
| } | |
| } | |
| var body: some View { | |
| GeometryReader { context in | |
| VStack(spacing: 0) { | |
| ForEach(self.list.values) { (node: InsertionSortArray.SortNode) in | |
| Rectangle() | |
| .frame(width: context.size.width, | |
| height: context.size.height / CGFloat(InsertionSortView.columnCount), | |
| alignment: .center) | |
| .foregroundColor(Color(node.color)) | |
| } | |
| }.animation(.default) | |
| .onTapGesture { | |
| self.handleTap() | |
| }.onReceive(self.pulse.publisher, perform: { _ in | |
| self.handleTap() | |
| }) | |
| .onLongPressGesture { | |
| self.pulse.toggle() | |
| } | |
| } | |
| } | |
| } | |
| struct InsertionSortView_Previews: PreviewProvider { | |
| static var previews: some View { | |
| InsertionSortView() | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment