How to fix timer for drawing initiated stopwatch? - ios

Hi there I am completing some research for Alzheimers - I want to be able to record the time it takes to complete a drawing. Both the time spend with apple pencil on tablet and the time spent overall to complete a drawing (time on tablet plus time in between strokes).
I have created this application so far but can't get the timer to work.
I have the drawing/scribble board down pat.
I have tried many different approaches but I am just not experienced enough in code to work out why it is not starting the timer when the apple pencil hits the tablet. The code below is for the ViewController script.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var canvasView: CanvasView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func clearCanvas(_ sender: Any) { //this button will clear canvas and clear timers!
canvasView.clearCanvas()
timer.invalidate()
seconds = 0 //Here we manually enter the restarting point for the seconds, but it would be wiser to make this a variable or constant.
timeDrawing.text = "\(seconds)"
}
#IBOutlet weak var timeDrawing: UILabel! //This is one of the label used to display the time drawing (i have yet to add the label to display the time inbetween strokes)
var seconds = 0
var timer = Timer()
var isTimerRunning = false //This will be used to make sure only one timer is created at a time.
var resumeTapped = false
var touchPoint:CGPoint!
var touches:UITouch!
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
}
#objc func updateTimer() {
seconds += 1
timeDrawing.text = "\(seconds)" //This will update the label.
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let touches = touch.location(in: canvasView) // or maybe ...(in: self)
if touch.type == .pencil {
if !isTimerRunning {
runTimer()
} else {
timer.invalidate()
}
}
}
}
I was hoping when the apple pencil touched the tablet it would start the timer. And then when the pencil left the tablet it would stop one of the timers. (i have yet to add another timer for the time inbetween strokes, any help with that would be appreciated too.)

As #ElTomato said you're looking for touchesEnded.
You have to move timer.invalidate() from touchesBegan to touchesEnded.
Also you should change your updateTimer logic for more high accuracy.
private(set) var from: Date?
#objc func updateTimer() {
let to = Date()
let timeIntervalFrom = (from ?? to).timeIntervalSince1970
let time = to.timeIntervalSince1970 - timeIntervalFrom
timeDrawing.text = "\(round(time))" //This will update the label.
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
guard let touch = touches.first else { return }
let touches = touch.location(in: canvasView) // or maybe ...(in: self)
if touch.type == .pencil {
if !isTimerRunning {
from = Date()
runTimer()
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
if !isTimerRunning {
timer.invalidate()
timer = nil
from = nil
timeDrawing.text = "0"
}
}

Here is how it works for the code below. 1. Drag a UIView control from the object library onto the storyboard. While this control is selected, enter 'TouchView' in the class field under the identity inspector. Make an IBOutlet connection to the view controller, naming it touchView.
import UIKit
class ViewController: UIViewController, TouchViewDelegate {
// MARK: - Variables
// MARK: - IBOutlet
#IBOutlet weak var touchView: TouchView!
// MARK: - IBAction
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
/* delegate */
touchView.touchViewDelegate = self
}
// MARK: - Protocol
var startDate: Date?
func touchBegan(date: Date) {
startDate = date
}
func touchEnd(date: Date) {
if let myDate = startDate {
let components = Calendar.current.dateComponents([.second], from: myDate, to: date)
if let secs = components.second {
print(secs)
}
}
}
}
import UIKit
protocol TouchViewDelegate: class {
func touchBegan(date: Date)
func touchEnd(date: Date)
}
class TouchView: UIView {
weak var touchViewDelegate: TouchViewDelegate?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = event?.allTouches?.first
if let touchPoint = touch?.location(in: self) {
if self.bounds.contains(touchPoint) {
Swift.print("You have started a session")
touchViewDelegate?.touchBegan(date: Date())
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
Swift.print("You have ended the session.")
touchViewDelegate?.touchEnd(date: Date())
}
}
So when the user touches the touch view, TouchView will make a delegate call to the view controller. When the user gets his finger off off the touch view, it will make another call to the view controller. And the view controller will calculate the difference between two dates. So you don't need a timer. It's my person opinion, but you should stay away from Timer unless you have no alternatives.

Related

How can I make an action to occur when two buttons are pressed at the same time?

I want my character to jump whenever I press two buttons at the same time. I've already tried this:
if rightButton.contains(location) && leftButton.contains(location) {
character.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 50))
}
One approach would be:
In your functions that detects the interaction with the button prepare it with a boolean.
Then in your Update function, use a timer to add a range of time where we can say that both buttons are pressed at the same time (100 ms for example).
I'll let you here some pseudocode that I hope it helps.
func RightBtnClick()->Void{
rightBtnPressed = true
}
func LeftBtnClick()->Void{
leftBtnPressed = true
}
func Start()->Void{
rightBtnTimer = 0
leftBtnTimer = 0
}
func Update(deltatime ms:float)->Void{
if(rightBtnPressed){
rightBtnTimer += ms;
if(rightBtnTimer>100){
rightBtnTimer = 0
rightBtnPressed=false
}
}
if(leftBtnPressed){
leftBtnTimer += ms;
if(leftBtnTimer>100){
leftBtnTimer = 0
leftBtnPressed=false
}
}
// Lastly let's check if both are pressed.
if(leftBtnPressed && rightBtnPressed){
DoStuff()
}
}
First of all, make sure in GameViewController.swift you have multitouch enabled.
class GameViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
// ...
if let view = self.view as! SKView?
{
// ...
view.isMultipleTouchEnabled = true
}
}
}
In GameScene give name to your buttons. On tap we will create a list of every node your fingers touched that has a name. If the list contains both right and left button, it means he pressed both at the same time.
class GameScene: SKScene
{
override func didMove(to view: SKView)
{
// add name to each button
left_button.name = "left_button"
right_button.name = "right_button"
}
func buttons_touched(_ touches: Set<UITouch>) -> [String]
{
// get the list of buttons we touched on each tap
var button_list : [String] = []
for touch in touches
{
let positionInScene = touch.location(in: self)
let touchedNode = self.nodes(at: positionInScene)
let buttons = touchedNode.compactMap { (node) -> String in
node.name ?? ""
}.filter({$0 != ""})
button_list += buttons
}
return button_list
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
let buttons_tapped = buttons_touched(touches)
if buttons_tapped.contains("right_button") && buttons_tapped.contains("left_button")
{
// jump code
}
}
}
You can simulate multitouch inside Simulator by holding Option button.

How can I use a button to get a touch input and output the location to a label in Swift 3?

I've just started playing with Swift, so apologies if this is a stupid question.
I'm trying to create a button so that when the user presses it and then touches the screen inside an image it will save the location of the touch as a CGPoint and change the text on a label to the coordinates.
So far I've got the below, but I'm not sure what arguments should be used to call the touchesBegan function from the IBAction of the button, or if I'm completely going about this the wrong way.
Any help would be greatly appreciated.
class FirstViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// *********** Set Coordinates ****************
// variable to be set as the location of the user's touch
var toploc:CGPoint? = nil
#IBOutlet weak var myImageView: UIImageView!
// label that will change to the coordinates of the touch
#IBOutlet weak var Topcoord: UILabel!
func touchesBegan(_ touches:Set<UITouch>, with event: UIEvent?) -> CGPoint {
if let touch = touches.first {
let position = touch.location(in: myImageView)
return position
} else {
// print("in else")
}
}
// button that stores location of user's touch and displays the coordinates in the Topcoord text
#IBAction func settop(_ sender: Any) {
toploc = touchesBegan(Set<UITouch>, UIEvent)
Topcoord.text = String(describing: toploc)
}
// ************** default stuff ***************
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
You don't need to call touchesBegan function from your code, because UIKit calls this method. When this method fired, just save the last location in your toploc variable and then use it in settop function when user will press the button. E.g.
var toploc: CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let position = touch?.location(in: myImageView) {
toploc = position //
}
}
#IBAction func settop(_ sender: Any)
{
topCoord.text = String(describing: toploc)
}
Сamel style is commonly used in Swift for names, first symbol in uppercase for types (and protocols) and lowercase for everything else. So, your code will look better if Topcoord variable name will change to 'topCoord' or 'topCoordinates'.
What you want is to use a UITapGestureRecognizer, which will actually give you the location of the touch. You can add it to your image view either in the storyboard/XIB or programmatically. E.g.
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewTapped(_:)))
myImageView.addGestureRecognizer(tapGestureRecognizer)
}
#IBAction func viewTapped(_ sender: UITapGestureRecognizer) {
switch sender.state {
case .ended:
Topcoord.text = "\(sender.location(ofTouch: 0, in: view))"
case .possible, .began, .changed, .cancelled, .failed:
break
}
}
P.S. You never create UITouch objects, they're created privately within UIKit.

How to constantly get the x-position of an moving object in swift?

I have to buttons leftbutton, rightbutton both of them being SKSpriteNode().
When the user touches one of the buttons, there is a little ship that moves left and right as long as the user holds the touch.
Now I need a function or anything else that gives me the ship.position.x the whole time. I am stuck to try to make it print the position constantly. I can make it print everytime the button is touched but it only prints it once.
In my didMove I only created the buttons and the ship. So it should be rather irrelevant.
func moveShip (moveBy: CGFloat, forTheKey: String) {
let moveAction = SKAction.moveBy(x: moveBy, y: 0, duration: 0.09)
let repeatForEver = SKAction.repeatForever(moveAction)
let movingSequence = SKAction.sequence([moveAction, repeatForEver])
ship.run(movingSequence, withKey: forTheKey)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("\(ship.position.x)")
for touch: AnyObject in touches {
let pointTouched = touch.location(in: self)
if leftButton.contains(pointTouched) {
// !! I MAKE IT PRINT THE POSITION HERE !!
moveShip(moveBy: -30, forTheKey: "leftButton")
}
else if rightButton.contains(pointTouched) {
moveShip(moveBy: 30, forTheKey: "rightButton")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node == aButton {
} else {
ship.removeAction(forKey: "leftButton")
ship.removeAction(forKey: "rightButton")
}
}
}
In my code, the position is only printed once at the beginning of the touch and not printed until you release the touch and touch it again. Is there a possible way to do this with my code?
The touchesMoved function won't help you with your particular problem. You can check your frame constantly by creating a var timer = Timer() as instance variable.
You then have to set up the timer and the function that is called when a specific amount of time is over.
Do as following in your didMove function:
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self,
selector: #selector(detectShipPosition), userInfo: nil, repeats: true)
As this will repeat itself every 0.01 seconds it will call the function detectShipPosition which you will implement OUTSIDE didMove.
func detectShipPosition(){
print("\(ship.position.x)")
}
Hi you can make use of the touchesMoved delegate method(It tells the responder when one or more touches associated with an event changed.) as follows,
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("\(ship.position.x)")
}
Solution for the comment you posted on Bharath answer.
You can change below with your own value:
longGesture.minimumPressDuration
You can use UILongPressGestureRecognizer :
class ViewController: UIViewController, UIGestureRecognizerDelegate {
#IBOutlet weak var leftButton: UIButton!
#IBOutlet weak var rightButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longPress(_:)))
longGesture.minimumPressDuration = 0.5
longGesture.delegate = self
leftButton.addGestureRecognizer(longGesture)
rightButton.addGestureRecognizer(longGesture)
}
#objc func longPress(_ gestureReconizer: UILongPressGestureRecognizer) {
print("\(ship.position.x)")
}
}
If you implement the update() function in your scene, it will be called for every frame and you can check your object's position (and existence) in there to print the value when it changes:
override func update(_ currentTime: TimeInterval)
{
if let ship = theShipSprite,
ship.position != lastPosition
{
print(ship.position) // or whatever it is you need to do
lastPosition = shipPosition
}
}

second view controller still react to first ViewController swift 3

I'm developing an app where I had to build my own gesture recognition (Apple's were too precise)
I need to use a second view controller (settings), and when I'm on the second one, it still react to the first one.
Where it's very problematic it's that with these gesture I control things in the house (such as lighting, music or even more), so imagine you click on a button on the second view and it light up the room, or you swipe down on the second view and the music is cut...
I'm using a segue to go to the SecondViewController
self.performSegue(withIdentifier: "SecondViewController", sender:self)
I've created another swift file for the second view controller, where, for now, there is nothing.
Do you have any idea what's going on and how I can solve that?
Thanks!
EDIT: As requested, here is more code :
import UIKit
class ViewController: UIViewController {
//Variables definition
override func viewDidLoad() {
super.viewDidLoad()
ascending = false
UIScreen.main.wantsSoftwareDimming = true
UIScreen.main.brightness = (0.0)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.timer.invalidate()
isPinched = false
super.touchesBegan(touches, with: event)
startTime = getCurrentMillis()
for touch in touches{
let point = touch.location(in: self.view)
for (index,finger) in fingers.enumerated() {
if finger == nil {
fingers[index] = String(format: "%p", touch)
if (finger1.isEmpty){
finger1 = [point.x, point.y]
}
//And so on until finger10
break
}
}
}
//If the gesture is long enough, I have a special recognition (longPress)
timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(increaseValue), userInfo: nil, repeats: true)
if ascending {
ascending = false
}
else {
ascending = true
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
for touch in touches {
let point = touch.location(in: self.view)
for (index,finger) in fingers.enumerated() {
if let finger = finger, finger == String(format: "%p", touch) {
switch (index){
case 0 :
finger1 += [point.x, point.y]
//And so on until case 9 / finger10
default :
break
}
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
endTime = getCurrentMillis()
for touch in touches {
for (index,finger) in fingers.enumerated() {
if let finger = finger, finger == String(format: "%p", touch) {
fingers[index] = nil
break
}
}
}
direction[0] = ""
direction[1] = ""
direction[2] = ""
direction[3] = ""
direction[4] = ""
if finger1.count != 0 {
direction[0] = GestureRecognizer(coordinates: finger1, index: 0)
}
if finger2.count != 0 {
direction[1] = GestureRecognizer(coordinates: finger2, index: 1)
}
if finger3.count != 0 {
direction[2] = GestureRecognizer(coordinates: finger3, index: 2)
}
if finger4.count != 0 {
direction[3] = GestureRecognizer(coordinates: finger4, index: 3)
}
if finger5.count != 0 {
direction[4] = GestureRecognizer(coordinates: finger5, index: 4)
}
if Int64(endTime - startTime) < 600 {
//My gesture recognition
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.directionLabel.text = self.labelUpdate
self.finger1 = []
self.finger2 = []
self.finger3 = []
self.finger4 = []
self.finger5 = []
self.finger6 = []
self.finger7 = []
self.finger8 = []
self.finger9 = []
self.finger10 = []
}
}
//One of the function I call
func swipeLeftTwo(){
request(urlAdress: "Url Adress")
}
func swipeRightFour(){
self.performSegue(withIdentifier: "SecondViewController", sender:self)
}
}
In the second view controller:
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var labelTe: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
labelTe.text = "something changed"
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

touchBegan and TouchEnded overrides are affecting another UIViewController

I have this UIViewController in which i've overrided the touchBegan and touchEnded functions. I also have a button that segueues (push) to another view controller with an SKView on it. But the overrided function in the first controller are still active ei. the calculations done there are still showing on the second ViewController.
Maybe theres something im missing or something im assuming thats wrong. Any help would be appreciated
This is the first view controller
import UIKit
import CoreMotion
class PortraitViewController: UIViewController {
var vc: UIViewController?
var startPoint: CGPoint?
var endPoint: CGPoint?
var movedPoint: CGPoint?
var previousMove = CGPoint(x: 0, y: 0)
var beginTouch: UITouch?
var scaleSum = 0
var isPortrait = true
let DEBUG: Bool = true
// MARK:
// MARK: Overriden Variables
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask{return .portrait}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{return .portrait}
override var shouldAutorotate: Bool {return false}
#IBAction func pinch(_ sender: UIPinchGestureRecognizer) {
if (isPortrait){
let scale = sender.scale
scaleSum += scale.exponent
print(scaleSum)
if(scaleSum > 10) {
scaleSum = 0
print(">10")
}
else if(scaleSum < -10) {
print("<10")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if(isPortrait){
if let theTouch = touches.first {
endPoint = theTouch.location(in: self.view)
let diffx = (endPoint!.x - startPoint!.x)
let diffy = (endPoint!.y - startPoint!.y)
if(diffx != 0 && diffy != 0){
let vector = CGVector(dx: diffx, dy: diffy)
var angle = atan2(vector.dy, vector.dx) * CGFloat(180.0 / M_PI)
if angle < 0 { angle *= -1 } else { angle = 360 - angle }
}
}
}
super.touchesEnded(touches, with: event)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if(isPortrait){
if let theTouch = touches.first {
startPoint = theTouch.location(in: self.view)
}
}
super.touchesBegan(touches, with: event)
}
//One of my attempts to jerry rig a solution
#IBAction func prepToLandscape(_ sender: UIBarButtonItem) {
self.isPortrait = false
print("isPortrait = \(self.isPortrait)")
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
navigationController?.setNavigationBarHidden(true, animated: false)
}
}
This is the second view controller
import UIKit
import CoreMotion
import SpriteKit
class LandScapeViewController: UIViewController {
var vc: UIViewController?
// MARK:
// MARK: Overriden Variables
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask{return .landscape}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{return .landscapeLeft
// MARK:
// MARK: Functions
override func viewDidLoad() {
super.viewDidLoad()
let controllerStoryBoard = UIStoryboard(name: "Main", bundle: nil)
vc = controllerStoryBoard.instantiateViewController(withIdentifier: "Root")
// Part of my attempt to jerry rig a solution
let vcP: PortraitViewController = UIStoryboard(name:"Controller",bundle: nil).instantiateViewController(withIdentifier: "PortraitController") as! PortraitViewController
vcP.isPortrait = false
print("vcP.isPortrait = \(vcP.isPortrait)")
}
override func viewWillAppear(_ animated: Bool) {
self.view.isMultipleTouchEnabled = true
let scene = GameScene(size: joystickView.bounds.size)
scene.backgroundColor = .gray
if let skView = joystickView as? SKView {
skView.showsFPS = false
skView.ignoresSiblingOrder = true
skView.backgroundColor = .red
skView.presentScene(scene)
}
navigationController?.setNavigationBarHidden(true, animated: false)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func toPortrait(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: {() -> Void in
}
}
Assuming the code shown is everything relevant, this makes sense. What's happening is this: the touch events are hit-tested to the joystickView, but because you haven't implemented a custom touchesBegan(_:with:) on that view, the touch event is passed up to the next UIResponder in the responder chain. That would be the LandScapeViewController. But that class also doesn't implement a custom touchesBegan(_:with:), so the event passes to the next class, which in this case is PortraitViewController. Because PortraitViewController does implement that method, it gets called. There's your confusion.
To fix this, implement the touches… methods on UIResponder for either joystickView or LandScapeViewController, even if they do nothing – but don't call super in them! Note the following, from the touchesBegan(_:with:) documentation:
If you override this method without calling super (a common use pattern), you must also override the other methods for handling touch events, even if your implementations do nothing.
Where you're overriding touchesBegan(_:with:), you probably don't want to call super. This is because the super implementation is the one that says "oh, shoot, I don't know how to handle this – pass it up the chain!" But when you handle the touch, it should end there, because you're handling it! So only call super when you're not handling the touch – which in your case looks like never, at least for PortraitViewController.
For more information, check out Event Delivery: The Responder Chain.

Resources