После выхода на рынок iPhone 6s и iPhone 6s Plus с экранами, которые поддерживают технологию 3D Touch, в App Store практически сразу появилось приложение для взвешивая слив и персиков.

image

Не могу с уверенностью сказать почему именно этих фруктов, но могу сказать однозначно почему именно фруктов. Дело в том, что сенсор экрана iPhone работает по принципу определения утечки тока с поверхности сенсора, а для этой самой утечки нужен живой палец либо что-то, что обладает электрической емкостью. Думаю, каждый знает, что на пластиковые стилус или ноготь экраны i-девайсов не срабатывают. Именно поэтому взвесить на том приложении что-то металлическое не получалось. Но фрукты имеют электрическую емкость, на них срабатывает сенсор и нормально срабатывает непосредственно 3D Touch.

Очень быстро это приложение было удалено из App Store. Лично мне кажется, что это было сделано из-за недалеких пользователей, которые попытались взвесить на своих устройствах пудовые гири. Разумеется, устройства сломались и они их понесли в сервисные центры. А там они сказали что-то из серии: «Приложение скачано из официального магазина, и там не предупреждали, что нельзя…».

В итоге, подобных приложений нет в магазине, но никто нам не помешает создать его для себя.

Задача


Нам нужно написать приложение, которое состоит из одного контроллера, на котором будут приглашающая надпись, нарисованный круг в центре экрана, индикаторы веса в граммах и процентов от определяемой силы (дальше по тексту будет понятнее). При нажатии на экран в месте касания будет появляться круг, который будет увеличиваться или уменьшаться в зависимости от силы нажатия. Сразу нужно сказать, что на симуляторе подобное приложение протестировать не получится. Поэтому нужно будет запускать приложение на реальном устройстве. По окончанию должно получиться вот такое приложение:

image

Создание проекта


Октройте XCode, выберите создание нового проекта, шаблон Single View Application

image

Построение интерфейса в Xcode


Перейдите в Storyboard, перетащите из библиотеки элементов на контроллер несколько UILabel, разместите их ближе к верхнему или нижнему краев контроллера. У меня получилось так:

image

Для эстетической привлекательности место куда будем класть предметы мы выделим красной окружностью. Можно взять уже готовую картинку с кругом, но это же не наш метод)). Круги мы нарисуем методами Core Graphics. Удобнее будет создать класс-наследник от UIView и уже с ним работать.

Добавьте в проект новый файл, назовите его ScaleView. Создайте в этом файле класс ScaleView который наследуется от UIView.

image

import UIKit
 
class ScaleView: UIView {
 
    override func draw(_ rect: CGRect) {
    }
 
}

Далее перейдите в StoryBoard, перенесите на контроллер из библиотеки элементов UIView и расположите его в центре нашего контроллера. Выберите только что добавленный UIView и в Identity Inspector задайте класс ScaleView, который мы создали ранее.

image

Также с помощью констрейнтов можно задать правила взаимного расположения элементов на экране. У меня это выглядит вот так:

image

Рисуем круги


Перейдите в файл ScaleView.swift. В классе ScaleView мы создали метод draw(_ rect:), который мы будем использовать для рисования внутри области отображения этого UIView.

Добавьте следующий код в метод draw(_ rect:)

override func draw(_ rect: CGRect) {
        
    let context = UIGraphicsGetCurrentContext() // 1
    context?.setStrokeColor(UIColor.red.cgColor) // 2
    context?.setLineWidth(14.0) // 3
    context?.addArc(center: CGPoint(x: 375 / 2, y: 375 / 2), radius: 375 / 2 - 14, startAngle: 0, endAngle: 2 * CGFloat(M_PI), clockwise: true) // 4
    context?.strokePath() // 5
}

  1. Получаем графический контекст, в котором мы буде рисовать
  2. Задаем цвет, которым будем рисовать. В данном случае. — это красный цвет
  3. Устанавливаем ширину линии, которой будем рисовать.
  4. Задаем путь для рисования в виде дуги, центр которой расположен в центре ScaleView, радиусом равным половине ширины ScaleView минус 14 ( это чтобы вписать дугу в видимую область View), и длинной дуги — по всей окружности в 360 градусов. Прошу учесть, что мои цифры ширины жестко заданы в предыдущем пункте с помощью констрейнтов.
  5. Рисуем по заданному пути заданными параметрами

Можно скомпилировать для проверки, однако также можно задать директиву для отображения изменений прямо в Interface Builder.

Вся магия в директиве @IBDesignable. Отметьте этой директивой класс ScaleView

import UIKit
 
@IBDesignable
class ScaleView: 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()
 
    }
}

После этого перейдите в StoryBoard, немного подождите и вы увидите нарисованную красную окружность в центре ViewController

image

Давайте потренируемся и нарисуем еще один круг поменьше и потоньше. Для этого в файле ScaleView в метод draw(_ rect:) добавьте следующий код:

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()

Думаю, понятно и так что мы добавили. По сути мы добавили еще одну окружность, серого цвета, радиусов в четверть ширины ScaleView и шириной в одну точку.

Результаты в StoryBoard:

image

Финалом наших подготовительных работ будет создание аутлетов для ScaleView и двух UILabel, который буду показывать силу нажатия на экран в процентах и вес в граммах. Ctrl-перетасктвние элементов из ViewController создаст нужные аутлеты.

@IBOutlet weak var scaleView: ScaleView!
 
@IBOutlet weak var forceLabel: UILabel!
    
@IBOutlet weak var grammLabel: UILabel!

Непосредственно — весы


Итак, мы вплотную подошли к моменту измерения силы нажатия на экран. Перейдите во ViewController и в методе viewDidLoad() добавьте стартовые значения для всех UILabel

override func viewDidLoad() {
 
    super.viewDidLoad()
 
    forceLabel.text = "0% force"
    grammLabel.text = "0 грамм"
}

Как и все процессы, связанные с нажатиями на экран, в контроллере их можно отловить в методе touchesMoved(_::). Данный метод срабатывает когда касания экрана происходят во времени. Т.е. Если палец стоит на экране или движется по нему срабатывает этот метод и можно отследить все касания и их свойства. Добавьте его во ViewController и напишите следующий код:

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first { // 1
            if #available(iOS 9.0, *) { // 2
                if traitCollection.forceTouchCapability == UIForceTouchCapability.available { // 3
                    if touch.force >= touch.maximumPossibleForce { // 4
                        forceLabel.text = "100%+ force"
                        grammLabel.text = "385 грамм"
                    } else {
                        
                        let force = (touch.force / touch.maximumPossibleForce) * 100 // 5
                        let grams = force * 385 / 100 // 6
                        let roundGrams = Int(grams) // 7
                        
                        forceLabel.text = "\(Int(force))% force" // 8
                        grammLabel.text = "\(roundGrams) грамм"
 
                  }
             }
         }
    }
}

Вся механика iOS приложения Весы заключается в этом методе. Все остальное, что мы будем делать дальше в этом уроке — это доработки. Всю основную работу мы уже сделали. Давайте разбирать по пунктам

  1. Из всего множества касаний экрана выберем первое
  2. Данная директива проверяет установленную операционную систему на устройстве и пропускает далее только если версия операционной системы 9.0 и более. Работа с 3D Touch стала возможной только с 9-ой версии iOS. Пытаться его обработать в боль ранних версиях не имеет смысла
  3. А в этой строке идет проверка устройства на поддержку экрана с функцией 3D Touch. Ведь iOS версии 10 может стоять и на iPhone 6, но от этого экран этого смартфона не начнет различать силу нажатия. Данную проверку необходимо проводить по строгому требованию Apple
  4. У касания есть свойство force в которе передается сила нажатия каждый раз, как срабатывает метод touchesMoved(_::). И в этой строке мы сравниваем значение текущей силы нажатия и максимально возможного значения силы нажатия. И если сила нажатия больше максимальной, то в наши UILabel мы передаем максимальные значения, а именно — 100 % силы и 385 грамм. Тут следует отметить почему именно 385 грамм. Дело в том, что технология 3D Touch сделана именно так, что 100% силы нажатия соответствуют 385-ти граммам. Соответственно получай процент силы нажатия мы можем легко вычислить вес в граммах.
  5. Вот тут эти вычисления и делаем. В этой строке вычисляем процент силы нажатия
  6. Тут вычислим вес в граммах, исходя из формулы 100% = 385 грамм
  7. Это простое округление граммов до целого
  8. Передаем значения процента силы и веса в граммах в наши UILabel

Прежде чем запускать и проверять приложение нужно добавьте еще один метод, который срабатывает в момент, когда все касания на экран прекращаются touchesEnded(::), для того чтобы задать начальное положение наших UILabel и передать в них значения 0% и 0 грамм. Добавьте этот метод в класс ViewController.

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        forceLabel.text = "0% force"
        grammLabel.text = "0 грамм"
}

Теперь можно компилировать приложение и проверять. Разумеется это нужно делать на реальном устройстве, чтобы увидеть результат. Симулятор не способен эмулировать силовые нажатия на экран.

image

Доработки


Основной функционал готов, но я при написании этого приложение решил добавить три вещи:

  1. При достижении максимального значения я хочу чтобы срабатывал виброотклик
  2. Обновление значений в UILabel происходят очень быстро, (я думаю вы это заметили при тестировании) поэтому нужно добавить некую плавность.
  3. В месте нажатия должен появляться полупрозрачный круг. Его диаметр должен увеличиваться по мере увеличения силы нажатия и уменьшаться по мере уменьшения силы нажатия

Этими дополнениями мы займемся в следующей статье :-)

Комментарии (27)


  1. SergeyMax
    15.07.2018 10:25
    +2

    И какова точность измерений?


    1. velkonost Автор
      15.07.2018 11:06
      +1

      Ради интереса, я сравнил с настоящими весами для еды. У меня получилось с точностью до сотых. Также можете проверить и вы, может ваш iPhone выдаст более точный результат)


      1. Source
        15.07.2018 22:17

        Зачем тогда округлять до целого?
        А вообще идея забавная!


    1. aquarium
      16.07.2018 09:50

      Золото взвешивать не получится, но для взвешивания соленых огурцов самое то.
      www.youtube.com/watch?v=iI5U3uK3dtY


      1. eatfears
        16.07.2018 11:07

        Так ведь огурчик с известной массой можно проложить, для золота.


  1. twelve
    15.07.2018 10:58
    +1

    А вы видели онлайн-весы для 3dTouch? http://www.touchscale.co


    1. velkonost Автор
      15.07.2018 11:09

      К сожалению, у меня не получилось пройти по вашей ссылке. Но я вовсе и не отрицаю, что данное приложение никто не придумал раньше) Статья ведь написана в образовательных целях


      1. twelve
        15.07.2018 12:56
        +1

        Там весы на JS, работающие в браузере. Я вас ни в коем случае не упрекаю, просто хотел показать ещё одну реализацию :)


      1. megahertz
        16.07.2018 10:21

        Видимо, сайт попал под раздачу РКН как и многие другие на DigitalOcean


  1. lasc
    15.07.2018 11:22
    +1

    Просто — порошок надо в ложку сыпать сначала, а не на экран!


    1. velkonost Автор
      15.07.2018 11:24
      +1

      Именно так. Если насыпать порошок на экран – устройство не отреагирует


  1. NeoCode
    15.07.2018 11:23
    +1

    Что бы там ни говорили про ненужность различных функций и наворотов, а стремлением пользователей к универсальному суперфункциональному устройству все-же есть. Данный пример это еще раз подтверждает.


  1. s_suhanov
    15.07.2018 11:31
    +4

    Симулятор не способен эмулировать силовые нажатия на экран.

    Если использовать макбукпро у которого есть форс-тач в тач-баре, то 3DTouch будет работать и в симуляторе.


  1. Dalein
    15.07.2018 12:23

    Подобные туториалы появлялись в большом количестве сразу после выхода iPhone 6s, в том числе и на хабре, зачем снова ворошить уже старую технологию, затем что сами о ней только узнали?


    1. Kicker
      15.07.2018 18:58
      +1

      А в чем проблема?



  1. syit
    16.07.2018 09:51

    спасибо за статью, интересная. У вас есть этот проект на гитхабе или еще где?


    1. velkonost Автор
      16.07.2018 09:52
      +1

      Есть) Я хотел опубликовать исходники во второй статье, тк там присутствуют функции, которые я еще не описал, но я с удовольствием могу поделиться и сейчас: github.com/Velkonost/iOS_Weights


      1. syit
        16.07.2018 12:23

        благодарю, буду играться :)


  1. citizentwo
    16.07.2018 09:52

    А можно после тренировки самому взвеситься?


    1. velkonost Автор
      16.07.2018 09:53

      Не думаю, что это хорошая идея. Если ваше устройство выдержит, то вы все равно будете весить 385 грамм, тк это максимальное измеряемое значение


  1. denis-19
    16.07.2018 09:53

    А максимальный безопасный для устройства вес какой?

    В программе бы указали, что большие и тяжелые это плохо — задействовать камеру и если там видно, что подносят большой фрукт\предмет — программа бы паниковала миганием как вариант.


    1. velkonost Автор
      16.07.2018 09:57

      Вообще, максимальное значение, которое может воспринять устройство – 385 грамм. Можно, конечно, положить предмет и потяжелее, но значение все равно останется то же. А так, не думаю, что это хорошая идея класть совсем тяжелые предметы на устройство)


      1. denis-19
        16.07.2018 10:06

        а можно в разные места экрана класть вишенки, например, чтобы потом понять сразу сколько весит каждая? типа выбрать побольше и слопать, а поменьше не есть :)


      1. POS_troi
        16.07.2018 10:09

        Сушить кошек в микроволновке то-же не разумная идея, но теперь то об этом пишут в всех инструкциях :))


  1. burzooom
    16.07.2018 10:12

    Я бы побоялся класть в рот еду, которая побывала на экране смартфона. Не каждый будет сначала протирать экран обеззараживающей жидостью


  1. BingoBongo
    16.07.2018 14:05

    Ну, идея настолько же экстраординарна, насколько бесполезна