3DTouch - Scales on the iPhone: Completion

  • Tutorial
In the last article, we sorted out the work with 3DTouch on the iPhone. It now remains to complete a couple of strokes and finish our application.

image

As I have already said, the main functionality is ready, there are only improvements:

  1. When the maximum value is reached, I want the vibration feedback to work as in the Haptic feedback article on iPhone 6s
  2. Updating the values ​​in UILabel happens very quickly (I think you noticed this when testing), so you need to add some smoothness.
  3. In the place of pressing should appear a translucent circle. Its diameter should increase as the pressing force increases and decrease as the pressing force decreases.

The first two items I can not display either using screenshots or using animated gif . Therefore, make the additions described below and test yourself on your devices. But I will demonstrate the third point. But let's make additions all together.

Vibration feedback Haptic feedback


We import the AudioToolbox framework before declaring the ViewController class , and also add the isPlaySound property to exclude multiple playback of the vibration feedback.

import UIKit
import AudioToolbox
classViewController: UIViewController{
    @IBOutletweakvar scaleView: ScaleView!@IBOutletweakvar forceLabel: UILabel!@IBOutletweakvar grammLabel: UILabel!var isPlaySound = true
...

Next, make changes to the touchesMoved (: :) method so that it looks like this:

overridefunctouchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        iflet touch = touches.first {
            cicrcleView.center = touch.location(in: view)
            if #available(iOS 9.0, *) {
                if traitCollection.forceTouchCapability == UIForceTouchCapability.available {
                    if touch.force >= touch.maximumPossibleForce {
                        forceLabel.text = "100%+ force"
                        grammLabel.text = "385 грамм"if isPlaySound {  // Добавления  // 1AudioServicesPlaySystemSound(1519)
                            isPlaySound = false// 2
                        }
                    } else {
                        let force = (touch.force / touch.maximumPossibleForce) * 100let grams = force * 385 / 100let roundGrams = Int(grams)
                        isPlaySound = true// Добавления // 3
                        forceLabel.text = "\(Int(force))% force"
                        grammLabel.text = "\(roundGrams) грамм"
                    }
                }
            }
        }
    }

Nothing complicated - when launching an iOS application, and in this case, when you initialize the ViewController and create a property of the isPlaySound class , we enable the ability to play sounds - including vibration feedback. When the maximum pressing force is reached, the isPlaySound (1) is checked, and if it is true , the vibration is performed and the prohibition (2) to play the vibration is immediately triggered. This prohibition is lifted (3) if the pressing force becomes less than the maximum possible value.

Smooth update


Now about the smoothness. Label updates occur very quickly, with the speed of the touchesMoved method (: :) , and these are several hundred hits per second. To reduce the frequency of updating the label, I added a property of the ViewController isUpdate class and set the observers for didSet properties .

var isUpdate = true {
        didSet {
            if isUpdate == false {
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
                    self.isUpdate = oldValue
                }
            }
        }
    }

The essence of this construction is that as soon as we set this property to false, it returns to true after 0.01 seconds. Accordingly, when writing text in UILabel, we will set the value of the isUpdate property to false and will not allow updating the captions until it becomes true . Therefore, updates to records will take place no more than once every hundredth of a second.

In the touchesMoved (: :) method in the branch, where we display the readings% strength and weight in grams, change the code as follows:

if isUpdate {
    forceLabel.text = "\(Int(force))% force"
    grammLabel.text = "\(roundGrams) грамм"
    isUpdate = false
}

This will be enough to give smoothness when updating the labels.

Touch visualization


First let's create a UIView and make it translucent and circular in shape. To do this, add a property of the ViewController class and make the initial settings in the viewDidLoad () method

let cicrcleView = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: 80)) // View 80 на 80overridefuncviewDidLoad() {
        super.viewDidLoad()
        forceLabel.text = "0% force"
        grammLabel.text = "0 грамм"
        cicrcleView.layer.cornerRadius = 40// Закруглили углы по половине ширины View - получлся круг
        cicrcleView.alpha = 0.6// Прозрачность 60%
        cicrcleView.backgroundColor = UIColor.red
    }

There is a property, it has a View , but at what point should it be added to the screen? It is logical that at the beginning of the touch. In ViewController you need to add the method touchesBegan (: :)

overridefunctouchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        iflet touch = touches.first {
            cicrcleView.center = touch.location(in: view) // 1
            view.addSubview(cicrcleView) // 2
        }
    }

Also choose from the set of tangencies first and work with this tangency.

  1. Set the coordinates of the center of the circleView to the touch point
  2. Add cicrcleView to the screen

In the touchesMoved (: :) method in the branch where the percentages and weight in grams are processed, add the line:

cicrcleView.transform = CGAffineTransform.init(scaleX: CGFloat(1 + (grams / 5) / 20), y: CGFloat(1 + (grams / 5) / 20))

Here we set the transformation matrix to increase the size of the cicrcleView in height and width. The values ​​that I passed to this matrix are the result of selecting the most convenient values. Selection by "spear". So you can experiment and choose values ​​that are convenient for you.

Well, finally, at the end of touches, you need to cancel the transformation for cicrleView and remove it from the screen. We already have a method where you can work it out. In the touchesEnded (: :) method, add two lines:

overridefunctouchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        forceLabel.text = "0% force"
        grammLabel.text = "0 грамм"// Добавить
        cicrcleView.removeFromSuperview() // Убрали круг
        cicrcleView.transform = .identity // Убрали трансформацию для круга
    }

The complete ViewController code looks like this:

import UIKit
import AudioToolbox
classViewController: UIViewController{
    @IBOutletweakvar scaleView: ScaleView!@IBOutletweakvar forceLabel: UILabel!@IBOutletweakvar grammLabel: UILabel!var isPlaySound = truevar isUpdate = true {
        didSet {
            if isUpdate == false {
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
                    self.isUpdate = oldValue
                }
            }
        }
    }
    let cicrcleView = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
    overridefuncviewDidLoad() {
        super.viewDidLoad()
        forceLabel.text = "0% force"
        grammLabel.text = "0 грамм"
        cicrcleView.layer.cornerRadius = 40
        cicrcleView.alpha = 0.6
        cicrcleView.backgroundColor = UIColor.red
    }
    overridefunctouchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        iflet touch = touches.first {
            cicrcleView.center = touch.location(in: view) // 1
            view.addSubview(cicrcleView) // 2
        }
    }
    overridefunctouchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        iflet touch = touches.first {
            cicrcleView.center = touch.location(in: view)
            if #available(iOS 9.0, *) {
                if traitCollection.forceTouchCapability == UIForceTouchCapability.available {
                    if touch.force >= touch.maximumPossibleForce {
                        forceLabel.text = "100%+ force"
                        grammLabel.text = "385 грамм"if isPlaySound {
                            AudioServicesPlaySystemSound(1519)
                            isPlaySound = false
                        }
                    } else {
                        let force = (touch.force / touch.maximumPossibleForce) * 100let grams = force * 385 / 100let roundGrams = Int(grams)
                        isPlaySound = trueif isUpdate {
                            forceLabel.text = "\(Int(force))% force"
                            grammLabel.text = "\(roundGrams) грамм"
                            isUpdate = false
                        }
                        cicrcleView.transform = CGAffineTransform.init(scaleX: CGFloat(1 + (grams / 5) / 20), y: CGFloat(1 + (grams / 5) / 20))
                    }
                }
            }
        }
    }
    overridefunctouchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        forceLabel.text = "0% force"
        grammLabel.text = "0 грамм"
        cicrcleView.removeFromSuperview()
        cicrcleView.transform = .identity
    }
}

ScaleView code :

import UIKit
@IBDesignableclassScaleView: UIView {
    override func draw(_rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        context?.setStrokeColor(UIColor.red.cgColor)
        context?.setLineWidth(14.0)
        context?.addArc(center: CGPoint(x: 375 / 2, y: 375 / 2), radius: 375 / 2 - 14, startAngle: 0, endAngle: 2 * CGFloat(M_PI), clockwise: true)
        context?.strokePath()
        context?.setLineWidth(1.0)
        context?.setStrokeColor(UIColor.lightGray.cgColor)
        context?.addArc(center: CGPoint(x: 375 / 2, y: 375 / 2), radius: 375 / 4 - 14, startAngle: 0, endAngle: 2 * CGFloat(M_PI), clockwise: true)
        context?.strokePath()
    }
}

Project Link to GitHub

Also popular now: