diff --git a/InductionApp.xcworkspace/xcuserdata/baltschuler.xcuserdatad/UserInterfaceState.xcuserstate b/InductionApp.xcworkspace/xcuserdata/baltschuler.xcuserdatad/UserInterfaceState.xcuserstate index 30e790df..de46a887 100644 Binary files a/InductionApp.xcworkspace/xcuserdata/baltschuler.xcuserdatad/UserInterfaceState.xcuserstate and b/InductionApp.xcworkspace/xcuserdata/baltschuler.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/inductionApp.xcodeproj/project.pbxproj b/inductionApp.xcodeproj/project.pbxproj index 34a086c8..d3627dae 100644 --- a/inductionApp.xcodeproj/project.pbxproj +++ b/inductionApp.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 2F945183244FFF7F009521F7 /* pdf_sat-practice-test-1.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 2F945182244FFF7F009521F7 /* pdf_sat-practice-test-1.pdf */; }; 2F9451852450009D009521F7 /* josh.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 2F9451842450009D009521F7 /* josh.jpeg */; }; 2F9C109C244F693100A7C6FF /* AppRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F9C109B244F693100A7C6FF /* AppRootView.swift */; }; + 2FAB7857245149570038DCF4 /* CanvasRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FAB7856245149570038DCF4 /* CanvasRepresentable.swift */; }; 2FEEE3FF2450B5B0003DE2BD /* AnswerSheetRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEEE3FE2450B5B0003DE2BD /* AnswerSheetRow.swift */; }; 2FEEE4012450B94C003DE2BD /* AnswerSheetList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEEE4002450B94C003DE2BD /* AnswerSheetList.swift */; }; D9DE365D4A1ADBF66FB1DE56 /* Pods_InductionApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7153C925F845E1499489472E /* Pods_InductionApp.framework */; }; @@ -47,6 +48,7 @@ 2F945182244FFF7F009521F7 /* pdf_sat-practice-test-1.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = "pdf_sat-practice-test-1.pdf"; sourceTree = ""; }; 2F9451842450009D009521F7 /* josh.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = josh.jpeg; sourceTree = ""; }; 2F9C109B244F693100A7C6FF /* AppRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppRootView.swift; path = InductionApp/AppRootView.swift; sourceTree = SOURCE_ROOT; }; + 2FAB7856245149570038DCF4 /* CanvasRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CanvasRepresentable.swift; path = InductionApp/CanvasRepresentable.swift; sourceTree = SOURCE_ROOT; }; 2FEEE3FE2450B5B0003DE2BD /* AnswerSheetRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AnswerSheetRow.swift; path = InductionApp/AnswerSheetRow.swift; sourceTree = SOURCE_ROOT; }; 2FEEE4002450B94C003DE2BD /* AnswerSheetList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AnswerSheetList.swift; path = InductionApp/AnswerSheetList.swift; sourceTree = SOURCE_ROOT; }; 7153C925F845E1499489472E /* Pods_InductionApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_InductionApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -89,15 +91,15 @@ children = ( 03BEBC4C244E7B69001576EC /* AppDelegate.swift */, 03BEBC4E244E7B69001576EC /* SceneDelegate.swift */, - 03BEBC50244E7B69001576EC /* ContentView.swift */, + 2FAB784C245135490038DCF4 /* Login and Signup */, 2F945180244FFA33009521F7 /* TestView.swift */, 2FEEE3FE2450B5B0003DE2BD /* AnswerSheetRow.swift */, + 2FAB7856245149570038DCF4 /* CanvasRepresentable.swift */, 2FEEE4002450B94C003DE2BD /* AnswerSheetList.swift */, 2F945182244FFF7F009521F7 /* pdf_sat-practice-test-1.pdf */, 2F9C109B244F693100A7C6FF /* AppRootView.swift */, 2F0DD2C7244F5F3D00AFFADE /* UserModel.swift */, 2F0DD2C9244F5F6300AFFADE /* TestModel.swift */, - 038753F7244E9AA900F85970 /* LoginView.swift */, 038753F9244E9AC800F85970 /* UserHomepageView.swift */, 2F9451842450009D009521F7 /* josh.jpeg */, 03BEBC52244E7B6A001576EC /* Assets.xcassets */, @@ -118,6 +120,15 @@ path = "Preview Content"; sourceTree = ""; }; + 2FAB784C245135490038DCF4 /* Login and Signup */ = { + isa = PBXGroup; + children = ( + 03BEBC50244E7B69001576EC /* ContentView.swift */, + 038753F7244E9AA900F85970 /* LoginView.swift */, + ); + path = "Login and Signup"; + sourceTree = ""; + }; 8AB41C41DEDE2DE06E9D7DE1 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -277,6 +288,7 @@ 2F0DD2CA244F5F6300AFFADE /* TestModel.swift in Sources */, 03BEBC4D244E7B69001576EC /* AppDelegate.swift in Sources */, 2FEEE4012450B94C003DE2BD /* AnswerSheetList.swift in Sources */, + 2FAB7857245149570038DCF4 /* CanvasRepresentable.swift in Sources */, 2FEEE3FF2450B5B0003DE2BD /* AnswerSheetRow.swift in Sources */, 03BEBC4F244E7B69001576EC /* SceneDelegate.swift in Sources */, 2F945181244FFA33009521F7 /* TestView.swift in Sources */, diff --git a/inductionApp/AnswerSheetRow.swift b/inductionApp/AnswerSheetRow.swift index 1c6e2c34..587f1052 100644 --- a/inductionApp/AnswerSheetRow.swift +++ b/inductionApp/AnswerSheetRow.swift @@ -16,6 +16,8 @@ struct AnswerSheetRow: View { var body: some View { VStack{ + + HStack{ Text("A") Spacer() diff --git a/inductionApp/CanvasRepresentable.swift b/inductionApp/CanvasRepresentable.swift new file mode 100644 index 00000000..2974173e --- /dev/null +++ b/inductionApp/CanvasRepresentable.swift @@ -0,0 +1,41 @@ +// +// DrawingPadRepresentation.swift +// InductionApp +// +// Created by Ben Altschuler on 4/22/20. +// Copyright © 2020 Josh Breite. All rights reserved. +// + +import Foundation +import SwiftUI +import PencilKit + + +struct CanvasRepresentable: UIViewRepresentable { + @Binding var canvasToDraw: PKCanvasView + +// class Coordinator: NSObject { +// @Binding var canvasToDraw: PKCanvasView? +// +// init(canvasToDraw: Binding){ +// _canvasToDraw = canvasToDraw +// } +// +//// @objc func drawingImageChanged(_ sender: CanvasView) { +//// self.drawingImage = sender.drawingImage +//// } +// } + + func makeUIView(context: Context) -> PKCanvasView { + let c = PKCanvasView() + c.isOpaque = false + c.allowsFingerDrawing = true + return c + } + + func updateUIView(_ uiView: PKCanvasView, context: Context) { + + } + + +} diff --git a/inductionApp/CanvasView.swift b/inductionApp/CanvasView.swift new file mode 100644 index 00000000..57264128 --- /dev/null +++ b/inductionApp/CanvasView.swift @@ -0,0 +1,89 @@ +// +// CanvasView.swift +// InductionApp +// +// Created by Ben Altschuler on 4/22/20. +// Copyright © 2020 Josh Breite. All rights reserved. +// + +import Foundation +import UIKit +import PencilKit + +class CanvasView: UIControl, PKCanvasViewDelegate { + var drawingImage: UIImage? + private let defaultLineWidth: CGFloat = 0.5 + private let minLineWidth: CGFloat = 0.4 + private let forceSensitivity: CGFloat = 1 + + var canvasViewPK: PKCanvasView + + init(drawingImage: UIImage?) { + self.drawingImage = drawingImage + self.canvasViewPK = PKCanvasView() + + super.init(frame: .zero) + self.addSubview(self.canvasViewPK) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func didMoveToWindow() { + canvasViewPK.delegate = self + print("BEN") + canvasViewPK.drawing = PKDrawing() + canvasViewPK.allowsFingerDrawing = false + } + + // MARK: Canvas View Delegate + + /// Delegate method: Note that the drawing has changed. + func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) { + print("Drawing Canvas View Drawing Did Change") + + } + + override func draw(_ rect: CGRect) { + drawingImage?.draw(in: rect) + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + sendActions(for: .valueChanged) + } + + override func touchesMoved(_ touches: Set, with event: UIEvent?) { + guard let touch = touches.first else { return } + drawingImage = UIGraphicsImageRenderer(size: bounds.size).image { context in + UIColor.white.setFill() + context.fill(bounds) + drawingImage?.draw(in: bounds) + var touches = [UITouch]() + if let coalescedTouches = event?.coalescedTouches(for: touch){ + touches = coalescedTouches + }else{ + touches.append(touch) + } + drawStroke(context: context.cgContext, touch: touch) + setNeedsDisplay() + } + } + + private func drawStroke(context: CGContext, touch: UITouch) { + let previousLocation = touch.previousLocation(in: self) + let location = touch.location(in: self) + + var lineWidth: CGFloat = defaultLineWidth + if touch.force > 0{ + lineWidth = touch.force * forceSensitivity + } + context.setLineWidth(lineWidth) + context.setLineCap(.round) + + + context.move(to: previousLocation) + context.addLine(to: location) + context.strokePath() + } +} diff --git a/inductionApp/Draw Old App/CGDrawingEngine.swift b/inductionApp/Draw Old App/CGDrawingEngine.swift new file mode 100644 index 00000000..031179c2 --- /dev/null +++ b/inductionApp/Draw Old App/CGDrawingEngine.swift @@ -0,0 +1,370 @@ +/* + See LICENSE folder for this sample’s licensing information. + + Abstract: + The view that is responsible for the drawing. StrokeCGView can draw a StrokeCollection as .calligraphy, .ink or .debug. + */ + +import UIKit + +enum StrokeViewDisplayOptions: CaseIterable, CustomStringConvertible { + case ink + + var description: String { + switch self { + case .ink: return "Ink" + } + } +} + +class StrokeCGView: UIView { + + var displayOptions = StrokeViewDisplayOptions.ink { + didSet { + if strokeCollection != nil { + setNeedsDisplay() + } + } + } + + var strokeCollection: StrokeCollection? { + didSet { + if oldValue !== strokeCollection { + setNeedsDisplay() + } + if let lastStroke = strokeCollection?.strokes.last { + setNeedsDisplay(for: lastStroke) + } + strokeToDraw = strokeCollection?.activeStroke + } + } + + var strokeToDraw: Stroke? { + didSet { + if oldValue !== strokeToDraw && oldValue != nil { + setNeedsDisplay() + } else { + if let stroke = strokeToDraw { + setNeedsDisplay(for: stroke) + } + } + } + } + + let strokeColor = UIColor.black + + // Hold samples when attempting to draw lines that are too short. + private var heldFromSample: StrokeSample? + private var heldFromSampleUnitVector: CGVector? + + private var lockedAzimuthUnitVector: CGVector? + private let azimuthLockAltitudeThreshold = CGFloat.pi / 2.0 * 0.80 // locking azimuth at 80% altitude + + // MARK: - Dirty rect calculation and handling. + var dirtyRectViews: [UIView]! + var lastEstimatedSample: (Int, StrokeSample)? + + func dirtyRects(for stroke: Stroke) -> [CGRect] { + var result = [CGRect]() + for range in stroke.updatedRanges() { + var lowerBound = range.lowerBound + if lowerBound > 0 { lowerBound -= 1 } + + if let (index, _) = lastEstimatedSample { + if index < lowerBound { + lowerBound = index + } + } + + let samples = stroke.samples + var upperBound = range.upperBound + if upperBound < samples.count { upperBound += 1 } + let dirtyRect = dirtyRectForSampleStride(stroke.samples[lowerBound..) -> CGRect { + var first = true + var frame = CGRect.zero + for sample in sampleStride { + let sampleFrame = CGRect(origin: sample.location, size: .zero) + if first { + first = false + frame = sampleFrame + } else { + frame = frame.union(sampleFrame) + } + } + let maxStrokeWidth = CGFloat(20.0) + return frame.insetBy(dx: -1 * maxStrokeWidth, dy: -1 * maxStrokeWidth) + } + + //Goes through each rect + func updateDirtyRects(for stroke: Stroke) { + let updateRanges = stroke.updatedRanges() + for (index, dirtyRectView) in dirtyRectViews.enumerated() { + if index < updateRanges.count { + dirtyRectView.alpha = 1.0 + dirtyRectView.frame = dirtyRectForSampleStride(stroke.samples[updateRanges[index]]) + } else { + dirtyRectView.alpha = 0.0 + } + } + } + + func setNeedsDisplay(for stroke: Stroke) { + for dirtyRect in dirtyRects(for: stroke) { + setNeedsDisplay(dirtyRect) + } + } + + // MARK: - Inits + + override init(frame: CGRect) { + super.init(frame: frame) + + layer.drawsAsynchronously = true + + let dirtyRectView = { () -> UIView in + let view = UIView(frame: CGRect(x: -10, y: -10, width: 0, height: 0)) + view.layer.borderColor = UIColor.red.cgColor + view.layer.borderWidth = 0.5 + view.isUserInteractionEnabled = false + view.isHidden = true + self.addSubview(view) + return view + } + dirtyRectViews = [dirtyRectView(), dirtyRectView()] + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +// MARK: - Drawing methods. + +extension StrokeCGView { + + override func draw(_ rect: CGRect) { +// UIColor.white.set() +// UIRectFill(rect) + print("OVERRRIDE DRAW") + // Optimization opportunity: Draw the existing collection in a different view, + // and only draw each time we add a stroke. + if let strokeCollection = strokeCollection { + for stroke in strokeCollection.strokes { + draw(stroke: stroke, in: rect) + } + } + + if let stroke = strokeToDraw { + draw(stroke: stroke, in: rect) + } + } + +} + +private extension StrokeCGView { + + /** + Note: this is not a particularily efficient way to draw a great stroke path + with CoreGraphics. It is just a way to produce an interesting looking result. + For a real world example you would reuse and cache CGPaths and draw longer + paths instead of an awful lot of tiny ones, etc. You would also respect the + draw rect to cull your draw requests. And you would use bezier paths to + interpolate between the points to get a smooother curve. + */ + func draw(stroke: Stroke, in rect: CGRect) { + print("OVERRRIDE DRAW") + + stroke.clearUpdateInfo() + + guard stroke.samples.isEmpty == false, + let context = UIGraphicsGetCurrentContext() + else { return } + + prepareToDraw() + lineSettings(in: context) + + if stroke.samples.count == 1 { + // Construct a fake segment to draw for a stroke that is only one point. + let sample = stroke.samples.first! + let tempSampleFrom = StrokeSample( + timestamp: sample.timestamp, + location: sample.location + CGVector(dx: -0.5, dy: 0.0), + coalesced: false, + predicted: false, + force: sample.force, + azimuth: sample.azimuth, + altitude: sample.altitude, + estimatedProperties: sample.estimatedProperties, + estimatedPropertiesExpectingUpdates: []) + + let tempSampleTo = StrokeSample( + timestamp: sample.timestamp, + location: sample.location + CGVector(dx: 0.5, dy: 0.0), + coalesced: false, + predicted: false, + force: sample.force, + azimuth: sample.azimuth, + altitude: sample.altitude, + estimatedProperties: + sample.estimatedProperties, + estimatedPropertiesExpectingUpdates: []) + + let segment = StrokeSegment(sample: tempSampleFrom) + segment.advanceWithSample(incomingSample: tempSampleTo) + segment.advanceWithSample(incomingSample: nil) + + draw(segment: segment, in: context) + } else { + for segment in stroke { + draw(segment: segment, in: context) + } + } + + } + + func draw(segment: StrokeSegment, in context: CGContext) { + print("OVERRRIDE DRAW") + + + guard let toSample = segment.toSample else { return } + + let fromSample: StrokeSample = heldFromSample ?? segment.fromSample + + // Skip line segments that are too short. + if (fromSample.location - toSample.location).quadrance < 0.003 { + if heldFromSample == nil { + heldFromSample = fromSample + heldFromSampleUnitVector = segment.fromSampleUnitNormal + } + return + } + + fillColor(in: context, toSample: toSample, fromSample: fromSample) + draw(segment: segment, in: context, toSample: toSample, fromSample: fromSample) + + if heldFromSample != nil { + heldFromSample = nil + heldFromSampleUnitVector = nil + } + } + + func draw(segment: StrokeSegment, + in context: CGContext, + toSample: StrokeSample, + fromSample: StrokeSample) { + debugPrint("OVERRRIDE DRAW") + + + let forceAccessBlock = self.forceAccessBlock() + + + let unitVector = heldFromSampleUnitVector != nil ? heldFromSampleUnitVector! : segment.fromSampleUnitNormal + let fromUnitVector = unitVector * forceAccessBlock(fromSample) + let toUnitVector = segment.toSampleUnitNormal * forceAccessBlock(toSample) + + let isForceEstimated = fromSample.estimatedProperties.contains(.force) || toSample.estimatedProperties.contains(.force) + if isForceEstimated { + if lastEstimatedSample == nil { + lastEstimatedSample = (segment.fromSampleIndex + 1, toSample) + } + forceEstimatedLineSettings(in: context) + } else { + lineSettings(in: context) + } + + context.beginPath() + context.addLines(between: [ + fromSample.location + fromUnitVector, + toSample.location + toUnitVector, + toSample.location - toUnitVector, + fromSample.location - fromUnitVector + ]) + context.closePath() + context.drawPath(using: .fillStroke) + + + + } + + + + func prepareToDraw() { + lastEstimatedSample = nil + heldFromSample = nil + heldFromSampleUnitVector = nil + lockedAzimuthUnitVector = nil + } + + func lineSettings(in context: CGContext) { + + + context.setLineWidth(0.25) + context.setStrokeColor(strokeColor.cgColor) + + + } + + func forceEstimatedLineSettings(in context: CGContext) { + lineSettings(in: context) + + } + + func azimuthSettings(in context: CGContext) { + context.setLineWidth(1.5) + context.setStrokeColor(#colorLiteral(red: 0, green: 0.7445889711, blue: 1, alpha: 1).cgColor) + } + + func altitudeSettings(in context: CGContext) { + context.setLineWidth(0.5) + context.setStrokeColor(strokeColor.cgColor) + } + + func forceAccessBlock() -> (_ sample: StrokeSample) -> CGFloat { + + var forceMultiplier = CGFloat(2.0) + var forceOffset = CGFloat(0.1) + var forceAccessBlock = {(sample: StrokeSample) -> CGFloat in + return sample.forceWithDefault + } + + if displayOptions == .ink { + forceAccessBlock = {(sample: StrokeSample) -> CGFloat in + return sample.perpendicularForce + } + } + + let previousGetter = forceAccessBlock + forceAccessBlock = {(sample: StrokeSample) -> CGFloat in + return previousGetter(sample) * forceMultiplier + forceOffset + } + + return forceAccessBlock + } + + func fillColor(in context: CGContext, toSample: StrokeSample, fromSample: StrokeSample) { + let fillColorRegular = UIColor.black.cgColor + let fillColorCoalesced = UIColor.lightGray.cgColor + let fillColorPredicted = UIColor.red.cgColor + context.setFillColor(fillColorRegular) + if toSample.predicted { + context.setFillColor(fillColorRegular) + } + } +} + + diff --git a/inductionApp/Draw Old App/CGMathExtensions.swift b/inductionApp/Draw Old App/CGMathExtensions.swift new file mode 100644 index 00000000..cb1f907a --- /dev/null +++ b/inductionApp/Draw Old App/CGMathExtensions.swift @@ -0,0 +1,112 @@ +/* +See LICENSE folder for this sample’s licensing information. + +Abstract: +Math extensions to Core Graphics structs. +*/ + +import Foundation +import CoreGraphics + +// MARK: - CGRect and Size + +extension CGRect { + var center: CGPoint { + get { + return origin + CGVector(dx: width, dy: height) / 2.0 + } + set { + origin = center - CGVector(dx: width, dy: height) / 2 + } + } +} + +func +(left: CGSize, right: CGFloat) -> CGSize { + return CGSize(width: left.width + right, height: left.height + right) +} + +func -(left: CGSize, right: CGFloat) -> CGSize { + return left + (-1.0 * right) +} + +// MARK: - CGPoint and CGVector math + +func -(left: CGPoint, right: CGPoint) -> CGVector { + return CGVector(dx: left.x - right.x, dy: left.y - right.y) +} + +func /(left: CGVector, right: CGFloat) -> CGVector { + return CGVector(dx: left.dx / right, dy: left.dy / right) +} + +func *(left: CGVector, right: CGFloat) -> CGVector { + return CGVector(dx: left.dx * right, dy: left.dy * right) +} + +func +(left: CGPoint, right: CGVector) -> CGPoint { + return CGPoint(x: left.x + right.dx, y: left.y + right.dy) +} + +func +(left: CGVector, right: CGVector) -> CGVector { + return CGVector(dx: left.dx + right.dx, dy: left.dy + right.dy) +} + +func +(left: CGVector?, right: CGVector?) -> CGVector? { + if let left = left, let right = right { + return CGVector(dx: left.dx + right.dx, dy: left.dy + right.dy) + } else { + return nil + } +} + +func -(left: CGPoint, right: CGVector) -> CGPoint { + return CGPoint(x: left.x - right.dx, y: left.y - right.dy) +} + +extension CGPoint { + init(_ vector: CGVector) { + self.init() + x = vector.dx + y = vector.dy + } +} + +extension CGVector { + init(_ point: CGPoint) { + self.init() + dx = point.x + dy = point.y + } + + func applying(_ transform: CGAffineTransform) -> CGVector { + return CGVector(CGPoint(self).applying(transform)) + } + + func rounding(toScale scale: CGFloat) -> CGVector { + return CGVector(dx: CoreGraphics.round(dx * scale) / scale, + dy: CoreGraphics.round(dy * scale) / scale) + } + + var quadrance: CGFloat { + return dx * dx + dy * dy + } + + var normal: CGVector? { + if !(dx.isZero && dy.isZero) { + return CGVector(dx: -dy, dy: dx) + } else { + return nil + } + } + + /// CGVector pointing in the same direction as self, with a length of 1.0 - or nil if the length is zero. + var normalized: CGVector? { + let quadrance = self.quadrance + if quadrance > 0.0 { + return self / sqrt(quadrance) + } else { + return nil + } + } +} + diff --git a/inductionApp/Draw Old App/DrawOnView.swift b/inductionApp/Draw Old App/DrawOnView.swift new file mode 100644 index 00000000..2098c523 --- /dev/null +++ b/inductionApp/Draw Old App/DrawOnView.swift @@ -0,0 +1,407 @@ +// +// DrawOnView.swift +// InductionApp +// +// Created by Ben Altschuler on 4/22/20. +// Copyright © 2020 Josh Breite. All rights reserved. +// + +import Foundation +import UIKit +import UIKit.UIGestureRecognizerSubclass + +class DrawOnView { + + init(view: UIView) { + self.view = view + let dirtyRectView = { () -> UIView in + let newView = UIView(frame: CGRect(x: -10, y: -10, width: 0, height: 0)) + newView.layer.borderColor = UIColor.red.cgColor + newView.layer.borderWidth = 0.5 + newView.isUserInteractionEnabled = false + newView.isHidden = true + view.addSubview(newView) + return newView + } + dirtyRectViews = [dirtyRectView(), dirtyRectView()] + } + + init(){ //context: CGContext + let dirtyRectView = { () -> UIView in + let newView = UIView(frame: CGRect(x: -10, y: -10, width: 0, height: 0)) + newView.layer.borderColor = UIColor.red.cgColor + newView.layer.borderWidth = 0.5 + newView.isUserInteractionEnabled = false + newView.isHidden = true + //view.addSubview(newView) + return newView + } + dirtyRectViews = [dirtyRectView(), dirtyRectView()] + //self.cgContext = context + } + var cgContext: CGContext? + var currentStrokeIndex: Int = 0 + var view: UIView? + //var strokeCollection: StrokeCollection = StrokeCollection() + + + var displayOptions = StrokeViewDisplayOptions.ink { //Part of Class + didSet { + if strokeCollection != nil { + view?.setNeedsDisplay() + } + } + } + + var strokeCollection: StrokeCollection? { + didSet { + if oldValue !== strokeCollection { + view?.setNeedsDisplay() + } + if let lastStroke = strokeCollection?.strokes.last { + + + setNeedsDisplay(for: lastStroke) + } + strokeToDraw = strokeCollection?.activeStroke + } + } + + var strokeToDraw: Stroke? { + didSet { + if oldValue !== strokeToDraw && oldValue != nil { + view?.setNeedsDisplay() + + } else { + if let stroke = strokeToDraw { + setNeedsDisplay(for: stroke) + + } + } + } + } + + let strokeColor = UIColor.black + + private var heldFromSample: StrokeSample? + private var heldFromSampleUnitVector: CGVector? + + private var lockedAzimuthUnitVector: CGVector? + private let azimuthLockAltitudeThreshold = CGFloat.pi / 2.0 * 0.80 // locking azimuth at 80% altitude + + // MARK: - Dirty rect calculation and handling. + var dirtyRectViews: [UIView]! + var lastEstimatedSample: (Int, StrokeSample)? + + + func updateView(view: UIView){ + self.currentStrokeIndex = 0 + self.view = view + } + func dirtyRects(for stroke: Stroke) -> [CGRect] { + var result = [CGRect]() + for range in stroke.updatedRanges() { + var lowerBound = range.lowerBound + if lowerBound > 0 { lowerBound -= 1 } + + if let (index, _) = lastEstimatedSample { + if index < lowerBound { + lowerBound = index + } + } + + let samples = stroke.samples + var upperBound = range.upperBound + if upperBound < samples.count { upperBound += 1 } + let dirtyRect = dirtyRectForSampleStride(stroke.samples[lowerBound..) -> CGRect { + var first = true + var frame = CGRect.zero + for sample in sampleStride { + let sampleFrame = CGRect(origin: sample.location, size: .zero) + if first { + first = false + frame = sampleFrame + } else { + frame = frame.union(sampleFrame) + } + } + let maxStrokeWidth = CGFloat(20.0) + return frame.insetBy(dx: -1 * maxStrokeWidth, dy: -1 * maxStrokeWidth) + } + + //Goes through each rect + func updateDirtyRects(for stroke: Stroke) { + let updateRanges = stroke.updatedRanges() + for (index, dirtyRectView) in dirtyRectViews.enumerated() { + if index < updateRanges.count { + dirtyRectView.alpha = 1.0 + dirtyRectView.frame = dirtyRectForSampleStride(stroke.samples[updateRanges[index]]) + } else { + dirtyRectView.alpha = 0.0 + } + } + } + + func setNeedsDisplay(for stroke: Stroke) { + for dirtyRect in dirtyRects(for: stroke) { + view?.setNeedsDisplay(dirtyRect) + } + } + + func receivedAllUpdatesForStroke(_ stroke: Stroke) { + + setNeedsDisplay(for: stroke) + stroke.clearUpdateInfo() + } + + + func strokeUpdated(strokeGesture: StrokeGestureRecognizer, isPencil: Bool ){ + var stroke: Stroke? + if strokeGesture.state != .cancelled { + stroke = strokeGesture.stroke + if strokeGesture.state == .began || + (strokeGesture.state == .ended && strokeCollection?.activeStroke == nil) { + strokeCollection?.activeStroke = stroke + } + } else { + strokeCollection?.activeStroke = nil + } + + if let stroke = stroke { + if strokeGesture.state == .ended { + if isPencil == true { + // Make sure we get the final stroke update if needed. + stroke.receivedAllNeededUpdatesBlock = { [weak self] in + self?.receivedAllUpdatesForStroke(stroke) + } + } + strokeCollection?.takeActiveStroke() + } + } + //answerCell.strokeCollection = strokeCollection + //self.cGStrokeCollection = answerCell.strokeCollection + } + + func beginDraw(rect: CGRect){ + if let strokeCollection = strokeCollection { + print("FIRRST BEGIN DRAW") + for stroke in strokeCollection.strokes { + draw(stroke: stroke) //, in: rect + } + } + + if let stroke = strokeToDraw { + print("SECOND BEGIN DRAW") + draw(stroke: stroke) //, in: rect + } + } + + func beginDrawContext(){ + print("BEGIN DRAW CONTEXT") + if let strokeCollection = strokeCollection { + for stroke in strokeCollection.strokes { + draw(stroke: stroke) + } + } + + if let stroke = strokeToDraw { + draw(stroke: stroke) + } + } + + func draw(stroke: Stroke) { //, in rect: CGRect + + stroke.clearUpdateInfo() + + guard stroke.samples.isEmpty == false, + let context = UIGraphicsGetCurrentContext() + else { return } + + prepareToDraw() + lineSettings(in: context) + + if stroke.samples.count == 1 { + // Construct a fake segment to draw for a stroke that is only one point. + let sample = stroke.samples.first! + let tempSampleFrom = StrokeSample( + timestamp: sample.timestamp, + location: sample.location + CGVector(dx: -0.5, dy: 0.0), + coalesced: false, + predicted: false, + force: sample.force, + azimuth: sample.azimuth, + altitude: sample.altitude, + estimatedProperties: sample.estimatedProperties, + estimatedPropertiesExpectingUpdates: []) + + let tempSampleTo = StrokeSample( + timestamp: sample.timestamp, + location: sample.location + CGVector(dx: 0.5, dy: 0.0), + coalesced: false, + predicted: false, + force: sample.force, + azimuth: sample.azimuth, + altitude: sample.altitude, + estimatedProperties: + sample.estimatedProperties, + estimatedPropertiesExpectingUpdates: []) + + let segment = StrokeSegment(sample: tempSampleFrom) + segment.advanceWithSample(incomingSample: tempSampleTo) + segment.advanceWithSample(incomingSample: nil) + + draw(segment: segment, in: context) + } else { + for segment in stroke { + draw(segment: segment, in: context) + } + } + + } + + func draw(segment: StrokeSegment, in context: CGContext) { + + guard let toSample = segment.toSample else { return } + + let fromSample: StrokeSample = heldFromSample ?? segment.fromSample + + // Skip line segments that are too short. + if (fromSample.location - toSample.location).quadrance < 0.003 { + if heldFromSample == nil { + heldFromSample = fromSample + heldFromSampleUnitVector = segment.fromSampleUnitNormal + } + return + } + + fillColor(in: context, toSample: toSample, fromSample: fromSample) + draw(segment: segment, in: context, toSample: toSample, fromSample: fromSample) + + if heldFromSample != nil { + heldFromSample = nil + heldFromSampleUnitVector = nil + } + } + + func draw(segment: StrokeSegment, + in context: CGContext, + toSample: StrokeSample, + fromSample: StrokeSample) { + + let forceAccessBlock = self.forceAccessBlock() + + + let unitVector = heldFromSampleUnitVector != nil ? heldFromSampleUnitVector! : segment.fromSampleUnitNormal + let fromUnitVector = unitVector * forceAccessBlock(fromSample) + let toUnitVector = segment.toSampleUnitNormal * forceAccessBlock(toSample) + + let isForceEstimated = fromSample.estimatedProperties.contains(.force) || toSample.estimatedProperties.contains(.force) + if isForceEstimated { + if lastEstimatedSample == nil { + lastEstimatedSample = (segment.fromSampleIndex + 1, toSample) + } + forceEstimatedLineSettings(in: context) + } else { + lineSettings(in: context) + } + + + context.beginPath() + context.addLines(between: [ + fromSample.location , //+ fromUnitVector, + toSample.location ,//+ toUnitVector, + toSample.location ,//- toUnitVector, + fromSample.location //- fromUnitVector //These help with Force. + ]) + + context.closePath() + context.drawPath(using: .fillStroke) + + + + + } + + + + func prepareToDraw() { + lastEstimatedSample = nil + heldFromSample = nil + heldFromSampleUnitVector = nil + lockedAzimuthUnitVector = nil + } + + func lineSettings(in context: CGContext) { + + + context.setLineWidth(2.0) + context.setStrokeColor(strokeColor.cgColor) + + + } + + func forceEstimatedLineSettings(in context: CGContext) { + lineSettings(in: context) + + } + + func azimuthSettings(in context: CGContext) { + context.setLineWidth(1.5) + context.setStrokeColor(#colorLiteral(red: 0, green: 0.7445889711, blue: 1, alpha: 1).cgColor) + } + + func altitudeSettings(in context: CGContext) { + context.setLineWidth(0.5) + context.setStrokeColor(strokeColor.cgColor) + } + + func forceAccessBlock() -> (_ sample: StrokeSample) -> CGFloat { + + var forceMultiplier = CGFloat(2.0) + var forceOffset = CGFloat(0.1) + var forceAccessBlock = {(sample: StrokeSample) -> CGFloat in + return sample.forceWithDefault + } + + if displayOptions == .ink { + forceAccessBlock = {(sample: StrokeSample) -> CGFloat in + return sample.perpendicularForce + } + } + + let previousGetter = forceAccessBlock + forceAccessBlock = {(sample: StrokeSample) -> CGFloat in + return previousGetter(sample) * forceMultiplier + forceOffset + } + + return forceAccessBlock + } + + func fillColor(in context: CGContext, toSample: StrokeSample, fromSample: StrokeSample) { + let fillColorRegular = UIColor.black.cgColor + let fillColorCoalesced = UIColor.lightGray.cgColor + let fillColorPredicted = UIColor.red.cgColor + context.setFillColor(fillColorRegular) + if toSample.predicted { + context.setFillColor(fillColorRegular) + } + } + +} + + + diff --git a/inductionApp/Draw Old App/DrawView.swift b/inductionApp/Draw Old App/DrawView.swift new file mode 100644 index 00000000..1b6e3954 --- /dev/null +++ b/inductionApp/Draw Old App/DrawView.swift @@ -0,0 +1,104 @@ +//// +//// DrawView.swift +//// InductionApp +//// +//// Created by Ben Altschuler on 4/22/20. +//// Copyright © 2020 Josh Breite. All rights reserved. +//// +// +//import Foundation +//import UIKit +//import UIKit.UIGestureRecognizerSubclass +//class DrawView: UIView, UIGestureRecognizerDelegate{ +// +// var drawingLayer = UIBezierPath() +// let path = CGMutablePath() +// +// var context: CGContext? +// +// +// var fingerStrokeRecognizer: StrokeGestureRecognizer! +// var pencilStrokeRecognizer: StrokeGestureRecognizer! +// +// var currentStrokeIndex: Int = 0 +// //var page: TestPage! +// +// override func layoutSubviews() { +// super.layoutSubviews() +// super.setNeedsDisplay() +// layer.drawsAsynchronously = true +// self.clipsToBounds = true +// self.isMultipleTouchEnabled = false +// +// self.fingerStrokeRecognizer = setupStrokeGestureRecognizer(isForPencil: false) +// self.pencilStrokeRecognizer = setupStrokeGestureRecognizer(isForPencil: true) +// +// } +// +// override func awakeFromNib() { +// super.awakeFromNib() +// +// } +// +// func setupStrokeGestureRecognizer(isForPencil: Bool) -> StrokeGestureRecognizer { +// let recognizer = StrokeGestureRecognizer(target: self, action: #selector(strokeUpdated(_:))) +// recognizer.delegate = self +// recognizer.cancelsTouchesInView = false +// self.addGestureRecognizer(recognizer) +// recognizer.coordinateSpaceView = self +// recognizer.isForPencil = isForPencil +// return recognizer +// } +// +// @objc +// func strokeUpdated(_ strokeGesture: StrokeGestureRecognizer?) { +// debugPrint("UPDATING STROKE For Test Page") +// // if strokeGesture === pencilStrokeRecognizer { +// // tableViewController.lastSeenPencilInteraction = Date() +// // } +// // +// +// var stroke: Stroke? +// if strokeGesture?.state != .cancelled { +// stroke = strokeGesture?.stroke +// if strokeGesture?.state == .began || +// (strokeGesture?.state == .ended && page.drawHelper.strokeCollection?.activeStroke == nil) { +// page.drawHelper.strokeCollection?.activeStroke = stroke +// } +// } else { +// page.drawHelper.strokeCollection?.activeStroke = nil +// } +// +// if let stroke = stroke { +// if strokeGesture?.state == .ended { +// if strokeGesture === pencilStrokeRecognizer { +// // Make sure we get the final stroke update if needed. +// stroke.receivedAllNeededUpdatesBlock = { [weak self] in +// self?.page.drawHelper.receivedAllUpdatesForStroke(stroke) +// } +// } +// page.drawHelper.strokeCollection?.takeActiveStroke() +// } +// } +// page.drawHelper.strokeCollection = page.drawHelper.strokeCollection +// setNeedsDisplay() +// +// } +// +// +// +// override func touchesEnded(_ touches: Set, with event: UIEvent?) { +// print("TOUCH ENDED TEST PAGE:") +// setNeedsDisplay() +// +// +// } +// +// +// override func draw(_ rect: CGRect) { +// debugPrint("DRAWING The Test Page") +// page?.drawHelper.beginDraw(rect: rect) //Draws all the strokes +// +// } +// +//} diff --git a/inductionApp/Draw Old App/StrokeCollection.swift b/inductionApp/Draw Old App/StrokeCollection.swift new file mode 100644 index 00000000..d4da1ef8 --- /dev/null +++ b/inductionApp/Draw Old App/StrokeCollection.swift @@ -0,0 +1,301 @@ +/* + See LICENSE folder for this sample’s licensing information. + + Abstract: + The Stroke data model and math extensions for CG primitives for easier math. + */ + +import Foundation +import UIKit + +class StrokeCollection { + var strokes: [Stroke] = [] + var activeStroke: Stroke? + + func takeActiveStroke() { + if let stroke = activeStroke { + strokes.append(stroke) + activeStroke = nil + } + } + + func clearCollection(){ + strokes = [] + activeStroke = nil + } +} + +enum StrokePhase { + case began + case changed + case ended + case cancelled +} + +struct StrokeSample { + // Always. + let timestamp: TimeInterval + let location: CGPoint + + // 3D Touch or Pencil. + var force: CGFloat? + + // Pencil only. + var estimatedProperties: UITouch.Properties = [] + var estimatedPropertiesExpectingUpdates: UITouch.Properties = [] + var altitude: CGFloat? + var azimuth: CGFloat? + + var azimuthUnitVector: CGVector { + var vector = CGVector(dx: 1.0, dy: 0.0) + if let azimuth = self.azimuth { + vector = vector.applying(CGAffineTransform(rotationAngle: azimuth)) + } + return vector + } + + init(timestamp: TimeInterval, + location: CGPoint, + coalesced: Bool, + predicted: Bool = false, + force: CGFloat? = nil, + azimuth: CGFloat? = nil, + altitude: CGFloat? = nil, + estimatedProperties: UITouch.Properties = [], + estimatedPropertiesExpectingUpdates: UITouch.Properties = []) { + + self.timestamp = timestamp + self.location = location + self.force = force + self.coalesced = coalesced + self.predicted = predicted + self.altitude = altitude + self.azimuth = azimuth + } + + /// Convenience accessor returns a non-optional (Default: 1.0) + var forceWithDefault: CGFloat { + return force ?? 1.0 + } + + /// Returns the force perpendicular to the screen. The regular pencil force is along the pencil axis. + var perpendicularForce: CGFloat { + let force = forceWithDefault + if let altitude = altitude { + let result = force / CGFloat(sin(Double(altitude))) + return result + } else { + return force + } + } + + // Values for debug display. + let coalesced: Bool + let predicted: Bool +} + +enum StrokeState { + case active + case done + case cancelled +} + +class Stroke { + static let calligraphyFallbackAzimuthUnitVector = CGVector(dx: 1.0, dy: 1.0).normalized! + + var samples: [StrokeSample] = [] + var predictedSamples: [StrokeSample] = [] + var previousPredictedSamples: [StrokeSample]? + var state: StrokeState = .active + var sampleIndicesExpectingUpdates = Set() + var expectsAltitudeAzimuthBackfill = false + var hasUpdatesFromStartTo: Int? + var hasUpdatesAtEndFrom: Int? + + var receivedAllNeededUpdatesBlock: (() -> Void)? + + func add(sample: StrokeSample) -> Int { + let resultIndex = samples.count + if hasUpdatesAtEndFrom == nil { + hasUpdatesAtEndFrom = resultIndex + } + samples.append(sample) + if previousPredictedSamples == nil { + previousPredictedSamples = predictedSamples + } + if sample.estimatedPropertiesExpectingUpdates != [] { + sampleIndicesExpectingUpdates.insert(resultIndex) + } + predictedSamples.removeAll() + return resultIndex + } + + func update(sample: StrokeSample, at index: Int) { + if index == 0 { + hasUpdatesFromStartTo = 0 + } else if hasUpdatesFromStartTo != nil && index == hasUpdatesFromStartTo! + 1 { + hasUpdatesFromStartTo = index + } else if hasUpdatesAtEndFrom == nil || hasUpdatesAtEndFrom! > index { + hasUpdatesAtEndFrom = index + } + samples[index] = sample + sampleIndicesExpectingUpdates.remove(index) + + if sampleIndicesExpectingUpdates.isEmpty { + if let block = receivedAllNeededUpdatesBlock { + receivedAllNeededUpdatesBlock = nil + block() + } + } + } + + func addPredicted(sample: StrokeSample) { + predictedSamples.append(sample) + } + + func clearUpdateInfo() { + hasUpdatesFromStartTo = nil + hasUpdatesAtEndFrom = nil + previousPredictedSamples = nil + } + + func updatedRanges() -> [CountableClosedRange] { + var ranges = [CountableClosedRange]() + + if let hasUpdatesFromStartTo = self.hasUpdatesFromStartTo, + let hasUpdatesAtEndFrom = self.hasUpdatesAtEndFrom { + ranges = [0...(hasUpdatesFromStartTo), hasUpdatesAtEndFrom...(samples.count - 1)] + + } else if let hasUpdatesFromStartTo = self.hasUpdatesFromStartTo { + ranges = [0...(hasUpdatesFromStartTo)] + + } else if let hasUpdatesAtEndFrom = self.hasUpdatesAtEndFrom { + ranges = [(hasUpdatesAtEndFrom)...(samples.count - 1)] + } + + return ranges + } + +} + +extension Stroke: Sequence { + func makeIterator() -> StrokeSegmentIterator { + return StrokeSegmentIterator(stroke: self) + } +} + +private func interpolatedNormalUnitVector(between vector1: CGVector, and vector2: CGVector) -> CGVector { + if let result = (vector1.normal + vector2.normal)?.normalized { + return result + } else { + // This means they resulted in a 0,0 vector, + // in this case one of the incoming vectors is a good result. + if let result = vector1.normalized { + return result + } else if let result = vector2.normalized { + return result + } else { + // This case should not happen. + return CGVector(dx: 1.0, dy: 0.0) + } + } +} + +class StrokeSegment { + var sampleBefore: StrokeSample? + var fromSample: StrokeSample! + var toSample: StrokeSample! + var sampleAfter: StrokeSample? + var fromSampleIndex: Int + + var segmentUnitNormal: CGVector { + return segmentStrokeVector.normal!.normalized! + } + + var fromSampleUnitNormal: CGVector { + return interpolatedNormalUnitVector(between: previousSegmentStrokeVector, and: segmentStrokeVector) + } + + var toSampleUnitNormal: CGVector { + return interpolatedNormalUnitVector(between: segmentStrokeVector, and: nextSegmentStrokeVector) + } + + var previousSegmentStrokeVector: CGVector { + if let sampleBefore = self.sampleBefore { + return fromSample.location - sampleBefore.location + } else { + return segmentStrokeVector + } + } + + var segmentStrokeVector: CGVector { + return toSample.location - fromSample.location + } + + var nextSegmentStrokeVector: CGVector { + if let sampleAfter = self.sampleAfter { + return sampleAfter.location - toSample.location + } else { + return segmentStrokeVector + } + } + + init(sample: StrokeSample) { + self.sampleAfter = sample + self.fromSampleIndex = -2 + } + + @discardableResult + func advanceWithSample(incomingSample: StrokeSample?) -> Bool { + if let sampleAfter = self.sampleAfter { + self.sampleBefore = fromSample + self.fromSample = toSample + self.toSample = sampleAfter + self.sampleAfter = incomingSample + self.fromSampleIndex += 1 + return true + } + return false + } +} + +class StrokeSegmentIterator: IteratorProtocol { + private let stroke: Stroke + private var nextIndex: Int + private let sampleCount: Int + private let predictedSampleCount: Int + private var segment: StrokeSegment! + + init(stroke: Stroke) { + self.stroke = stroke + nextIndex = 1 + sampleCount = stroke.samples.count + predictedSampleCount = stroke.predictedSamples.count + if (predictedSampleCount + sampleCount) > 1 { + segment = StrokeSegment(sample: sampleAt(0)!) + segment.advanceWithSample(incomingSample: sampleAt(1)) + } + } + + func sampleAt(_ index: Int) -> StrokeSample? { + if index < sampleCount { + return stroke.samples[index] + } + let predictedIndex = index - sampleCount + if predictedIndex < predictedSampleCount { + return stroke.predictedSamples[predictedIndex] + } else { + return nil + } + } + + func next() -> StrokeSegment? { + nextIndex += 1 + if let segment = self.segment { + if segment.advanceWithSample(incomingSample: sampleAt(nextIndex)) { + return segment + } + } + return nil + } +} diff --git a/inductionApp/Draw Old App/StrokeGestureRecognizer.swift b/inductionApp/Draw Old App/StrokeGestureRecognizer.swift new file mode 100644 index 00000000..6e568a09 --- /dev/null +++ b/inductionApp/Draw Old App/StrokeGestureRecognizer.swift @@ -0,0 +1,276 @@ +/* + See LICENSE folder for this sample’s licensing information. + + Abstract: + The custom UIGestureRecognizer subclass to capture strokes. + */ + +import UIKit +import UIKit.UIGestureRecognizerSubclass + +/// A custom gesture recognizer that receives touch events and appends data to the stroke sample. +/// - Tag: StrokeGestureRecognizer + +protocol DrawingGestureRecognizerDelegate: class { + func gestureRecognizerBegan(_ location: CGPoint) + func gestureRecognizerMoved(_ location: CGPoint) + func gestureRecognizerEnded(_ location: CGPoint) +} + + +class StrokeGestureRecognizer: UIGestureRecognizer { + // MARK: - Configuration + var collectsCoalescedTouches = true + var usesPredictedSamples = true + + /// A Boolean value that determines whether the gesture recognizer tracks Apple Pencil or finger touches. + /// - Tag: isForPencil + var isForPencil: Bool = false { + didSet { + if isForPencil { + allowedTouchTypes = [UITouch.TouchType.pencil.rawValue as NSNumber] + } else { + allowedTouchTypes = [UITouch.TouchType.direct.rawValue as NSNumber] + } + } + } + + // MARK: - Data + var stroke = Stroke() + var outstandingUpdateIndexes = [Int: (Stroke, Int)]() + var coordinateSpaceView: UIView? + + // MARK: - State + var trackedTouch: UITouch? + var initialTimestamp: TimeInterval? + var collectForce = false + + var fingerStartTimer: Timer? + private let cancellationTimeInterval = TimeInterval(0.1) + + // MARK: -FOR PDF + weak var drawingDelegate: DrawingGestureRecognizerDelegate? + + var ensuredReferenceView: UIView { + if let view = coordinateSpaceView { + return view + } else { + return view! + } + } + + // MARK: - Stroke data collection + + /// Appends touch data to the stroke sample. + /// - Tag: appendTouches + func append(touches: Set, event: UIEvent?) -> Bool { + // Check that we have a touch to append, and that touches + // doesn't contain it. + //debugPrint("Append Function First") + + guard let touchToAppend = trackedTouch, touches.contains(touchToAppend) == true + else { return false } + + // Cancel the stroke recognition if we get a second touch during cancellation period. + if shouldCancelRecognition(touches: touches, touchToAppend: touchToAppend) { + if state == .possible { + state = .failed + } else { + state = .cancelled + } + return false + } + + let view = ensuredReferenceView + if collectsCoalescedTouches { + if let event = event { + let coalescedTouches = event.coalescedTouches(for: touchToAppend)! + let lastIndex = coalescedTouches.count - 1 + for index in 0.., touchToAppend: UITouch) -> Bool { + var shouldCancel = false + for touch in touches { + if touch !== touchToAppend && + touch.timestamp - initialTimestamp! < cancellationTimeInterval { + shouldCancel = true + break + } + } + return shouldCancel + } + + func saveStrokeSample(stroke: Stroke, touch: UITouch, view: UIView, coalesced: Bool, predicted: Bool ) { + // Only collect samples that actually moved in 2D space. + let location = touch.preciseLocation(in: view) + if let previousSample = stroke.samples.last { + if (previousSample.location - location).quadrance < 0.003 { + return + } + } + + var sample = StrokeSample(timestamp: touch.timestamp, + location: location, + coalesced: coalesced, + predicted: predicted, + force: self.collectForce ? touch.force : nil) + + if touch.type == .pencil { + let estimatedProperties = touch.estimatedProperties + sample.estimatedProperties = estimatedProperties + sample.estimatedPropertiesExpectingUpdates = touch.estimatedPropertiesExpectingUpdates + sample.altitude = touch.altitudeAngle + sample.azimuth = touch.azimuthAngle(in: view) + if stroke.samples.isEmpty && + estimatedProperties.contains(.azimuth) { + stroke.expectsAltitudeAzimuthBackfill = true + } else if stroke.expectsAltitudeAzimuthBackfill && + !estimatedProperties.contains(.azimuth) { + for (index, priorSample) in stroke.samples.enumerated() { + var updatedSample = priorSample + if updatedSample.estimatedProperties.contains(.altitude) { + updatedSample.estimatedProperties.remove(.altitude) + updatedSample.altitude = sample.altitude + } + if updatedSample.estimatedProperties.contains(.azimuth) { + updatedSample.estimatedProperties.remove(.azimuth) + updatedSample.azimuth = sample.azimuth + } + stroke.update(sample: updatedSample, at: index) + } + stroke.expectsAltitudeAzimuthBackfill = false + } + } + + if predicted { + stroke.addPredicted(sample: sample) + } else { + let index = stroke.add(sample: sample) + if touch.estimatedPropertiesExpectingUpdates != [] { + if let estimationUpdateIndex = touch.estimationUpdateIndex { + self.outstandingUpdateIndexes[Int(estimationUpdateIndex.intValue)] = (stroke, index) + } + } + } + } + + // MARK: - Touch handling methods + + /// A set of functions that track touches. + /// - Tag: HandleTouches + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + if trackedTouch == nil { + trackedTouch = touches.first + initialTimestamp = trackedTouch?.timestamp + collectForce = trackedTouch!.type == .pencil || view?.traitCollection.forceTouchCapability == .available + if !isForPencil { + // Give other gestures, such as pan and pinch, a chance by + // slightly delaying the `.begin. + fingerStartTimer = Timer.scheduledTimer( + withTimeInterval: cancellationTimeInterval, + repeats: false, + block: { [weak self] (timer) in + guard let strongSelf = self else { return } + if strongSelf.state == .possible { + strongSelf.state = .began + } + }) + } + } + if append(touches: touches, event: event) { +// debugPrint("Appending \(isForPencil)") + if isForPencil { + state = .began + } + } + + let location = trackedTouch?.location(in: self.view) + drawingDelegate?.gestureRecognizerBegan(location!) + + } + + override func touchesMoved(_ touches: Set, with event: UIEvent?) { + if append(touches: touches, event: event) { + if state == .began { + debugPrint("Touch Moved: \(state)") + } + } + guard let location = touches.first?.location(in: self.view) else { return } + drawingDelegate?.gestureRecognizerMoved(location) + + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + if append(touches: touches, event: event) { + stroke.state = .done + state = .ended + } + guard let location = touches.first?.location(in: self.view) else { + state = .ended + return + } + drawingDelegate?.gestureRecognizerEnded(location) + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + if append(touches: touches, event: event) { + debugPrint("Touch Cancelled?") + stroke.state = .cancelled + state = .failed + } + } + + /// Replaces previously estimated touch data with updated touch data. + /// - Tag: estimatedPropertiesUpdated + override func touchesEstimatedPropertiesUpdated(_ touches: Set) { + for touch in touches { + guard let index = touch.estimationUpdateIndex else { + continue + } + if let (stroke, sampleIndex) = outstandingUpdateIndexes[Int(index.intValue)] { + var sample = stroke.samples[sampleIndex] + let expectedUpdates = sample.estimatedPropertiesExpectingUpdates + if expectedUpdates.contains(.force) { + sample.force = touch.force + if !touch.estimatedProperties.contains(.force) { + // Only remove the estimate flag if the new value isn't estimated as well. + sample.estimatedProperties.remove(.force) + } + } + sample.estimatedPropertiesExpectingUpdates = touch.estimatedPropertiesExpectingUpdates + if touch.estimatedPropertiesExpectingUpdates == [] { + outstandingUpdateIndexes.removeValue(forKey: sampleIndex) + } + stroke.update(sample: sample, at: sampleIndex) + } + } + } + + override func reset() { + stroke = Stroke() + trackedTouch = nil + if let timer = fingerStartTimer { + timer.invalidate() + fingerStartTimer = nil + } + super.reset() + } +} diff --git a/inductionApp/Draw/CanvasView.swift b/inductionApp/Draw/CanvasView.swift new file mode 100644 index 00000000..b1393f7a --- /dev/null +++ b/inductionApp/Draw/CanvasView.swift @@ -0,0 +1,23 @@ +// +// CanvasView.swift +// InductionApp +// +// Created by Ben Altschuler on 4/22/20. +// Copyright © 2020 Josh Breite. All rights reserved. +// + +import Foundation +import UIKit + +class CanvasView: UIControl { + var drawingImage: UIImage? + + init(drawingImage: UIImage?){ + self.drawingImage = drawingImage + super.init(frame: .zero) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/inductionApp/Draw/DrawingPad.swift b/inductionApp/Draw/DrawingPad.swift new file mode 100644 index 00000000..ea2b2c2c --- /dev/null +++ b/inductionApp/Draw/DrawingPad.swift @@ -0,0 +1,23 @@ +// +// DrawingPad.swift +// InductionApp +// +// Created by Ben Altschuler on 4/22/20. +// Copyright © 2020 Josh Breite. All rights reserved. +// + +import SwiftUI + +struct DrawingPad: View { + @State var drawingImage: UIImage? + + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +struct DrawingPad_Previews: PreviewProvider { + static var previews: some View { + DrawingPad() + } +} diff --git a/inductionApp/Drawing Engine/DrawView.swift b/inductionApp/Drawing Engine/DrawView.swift new file mode 100644 index 00000000..95ddc714 --- /dev/null +++ b/inductionApp/Drawing Engine/DrawView.swift @@ -0,0 +1,104 @@ +// +// DrawView.swift +// InductionApp +// +// Created by Ben Altschuler on 4/22/20. +// Copyright © 2020 Josh Breite. All rights reserved. +// + +import Foundation +import UIKit +import UIKit.UIGestureRecognizerSubclass +class DrawView: UIView, UIGestureRecognizerDelegate{ + + var drawingLayer = UIBezierPath() + let path = CGMutablePath() + + var context: CGContext? + + + var fingerStrokeRecognizer: StrokeGestureRecognizer! + var pencilStrokeRecognizer: StrokeGestureRecognizer! + + var currentStrokeIndex: Int = 0 + var page: TestPage! + + override func layoutSubviews() { + super.layoutSubviews() + super.setNeedsDisplay() + layer.drawsAsynchronously = true + self.clipsToBounds = true + self.isMultipleTouchEnabled = false + + self.fingerStrokeRecognizer = setupStrokeGestureRecognizer(isForPencil: false) + self.pencilStrokeRecognizer = setupStrokeGestureRecognizer(isForPencil: true) + + } + + override func awakeFromNib() { + super.awakeFromNib() + + } + + func setupStrokeGestureRecognizer(isForPencil: Bool) -> StrokeGestureRecognizer { + let recognizer = StrokeGestureRecognizer(target: self, action: #selector(strokeUpdated(_:))) + recognizer.delegate = self + recognizer.cancelsTouchesInView = false + self.addGestureRecognizer(recognizer) + recognizer.coordinateSpaceView = self + recognizer.isForPencil = isForPencil + return recognizer + } + + @objc + func strokeUpdated(_ strokeGesture: StrokeGestureRecognizer?) { + debugPrint("UPDATING STROKE For Test Page") + // if strokeGesture === pencilStrokeRecognizer { + // tableViewController.lastSeenPencilInteraction = Date() + // } + // + + var stroke: Stroke? + if strokeGesture?.state != .cancelled { + stroke = strokeGesture?.stroke + if strokeGesture?.state == .began || + (strokeGesture?.state == .ended && page.drawHelper.strokeCollection?.activeStroke == nil) { + page.drawHelper.strokeCollection?.activeStroke = stroke + } + } else { + page.drawHelper.strokeCollection?.activeStroke = nil + } + + if let stroke = stroke { + if strokeGesture?.state == .ended { + if strokeGesture === pencilStrokeRecognizer { + // Make sure we get the final stroke update if needed. + stroke.receivedAllNeededUpdatesBlock = { [weak self] in + self?.page.drawHelper.receivedAllUpdatesForStroke(stroke) + } + } + page.drawHelper.strokeCollection?.takeActiveStroke() + } + } + page.drawHelper.strokeCollection = page.drawHelper.strokeCollection + setNeedsDisplay() + + } + + + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + print("TOUCH ENDED TEST PAGE:") + setNeedsDisplay() + + + } + + + override func draw(_ rect: CGRect) { + debugPrint("DRAWING The Test Page") + page?.drawHelper.beginDraw(rect: rect) //Draws all the strokes + + } + +} diff --git a/inductionApp/ContentView.swift b/inductionApp/Login and Signup/ContentView.swift similarity index 100% rename from inductionApp/ContentView.swift rename to inductionApp/Login and Signup/ContentView.swift diff --git a/inductionApp/LoginView.swift b/inductionApp/Login and Signup/LoginView.swift similarity index 100% rename from inductionApp/LoginView.swift rename to inductionApp/Login and Signup/LoginView.swift diff --git a/inductionApp/SceneDelegate.swift b/inductionApp/SceneDelegate.swift index 60907426..b8962025 100644 --- a/inductionApp/SceneDelegate.swift +++ b/inductionApp/SceneDelegate.swift @@ -22,11 +22,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var manager = UserAuth() // Create the SwiftUI view that provides the window contents. - + // Use a UIHostingController as window root view controller. if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = UIHostingController(rootView: AppRootView().environmentObject(manager)) + //window.rootViewController = UIHostingController(rootView: DrawingPad()) self.window = window window.makeKeyAndVisible() } diff --git a/inductionApp/TestView.swift b/inductionApp/TestView.swift index 6f1085db..f8862073 100644 --- a/inductionApp/TestView.swift +++ b/inductionApp/TestView.swift @@ -7,18 +7,19 @@ // import SwiftUI +import PencilKit struct TestView: View { let pages = testPDF().pages - + @Environment(\.undoManager) var undoManager var body: some View { HStack{ AnswerSheetList().frame(width: 300) ScrollView { VStack { - ForEach(pages, id: \.self){ image in - Image(uiImage: image.uiImage).resizable().aspectRatio(contentMode: .fill) + ForEach(pages, id: \.self){ page in + PageView(model: page) } } @@ -27,27 +28,50 @@ struct TestView: View { } } +//struct DrawViewUI: UIViewRepresentable { +// @Binding var drawView: DrawView +// +// func makeUIView(context: Context) -> DrawView { +// return drawView +// } +// +// func updateUIView(_ uiView: PKCanvasView, context: Context) { } +//} +struct PageView: View{ + var model: PageModel + @State private var canvas: PKCanvasView = PKCanvasView() + + var body: some View { + ZStack{ + Image(uiImage: model.uiImage).resizable().aspectRatio(contentMode: .fill) + CanvasRepresentable(canvasToDraw: $canvas) + } + } +} + struct TestView_Previews: PreviewProvider { static var previews: some View { TestView() } } -struct Page: Hashable { - var id: Int - var uiImage: UIImage + + +struct PageModel: Hashable { + var uiImage: UIImage + var id: Int } class testPDF { - var pages = [Page]() + var pages = [PageModel]() init(){ var pageCounter = 1 let path = Bundle.main.path(forResource: "pdf_sat-practice-test-1", ofType: "pdf") let url = URL(fileURLWithPath: path!) while let pdfImage = createUIImage(url: url, page: pageCounter){ - pages.append(Page(id: pageCounter - 1, uiImage: pdfImage)) + pages.append(PageModel(uiImage: pdfImage, id: pageCounter - 1)) pageCounter = pageCounter + 1 if (pageCounter>30){ //TODO: Get rid of this. Figure out why the PDF file is corrupted break diff --git a/inductionApp/UserHomepageView.swift b/inductionApp/UserHomepageView.swift index 86c98e3c..45800d8b 100644 --- a/inductionApp/UserHomepageView.swift +++ b/inductionApp/UserHomepageView.swift @@ -112,6 +112,7 @@ struct UserHomepageView: View { } } }.navigationViewStyle(StackNavigationViewStyle()) + } }