The iPhone accelerometer's axes in three dimensions

Глава 10. Отслеживание движения и ориентации в SwiftUI

Оглавление

Глава 10. Отслеживание движения и ориентации в SwiftUI

Что такое Core Motion?

Акселерометр, iOs и  SwiftUI

Отслеживание вращения с помощью гироскопа

Измеряем магнитные поля

Обнаружение перемещения устройства

Заключение

Глава 10. Отслеживание движения и ориентации в SwiftUI

Мобильные устройства, такие как iPhone или iPad – это полноценные мобильные компьютеры, которые мы носим с собой в кармане или в сумке, где бы мы не были. Однако в отличие от настольных компьютеров или ноутбуков, мобильные компьютеры могут отслеживать движение и положение в пространстве. Используя Apple API задача отслеживания перемещений рук пользователя в играх или измерение углов положения девайса в пространстве – для  Вас будет весьма проста в выполнении.

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

Что такое Core Motion?

Если Вы хотите отслеживать что то, помимо простых перемещений, компания Apple предоставила программный фреймворк, называемый Core Motion(Ядро перемещений). Core Motion предоставляет программный доступ для отслеживания следующих типов пермещений:

  • Ускорение в трех измерениях
  • Вращение вокруг осей x, y, z
  • Датчик отслеживания магнитных полей, который отслеживает положение устройства по отношению к магнитному полю Земли
  • Датчик перемещения устройства относительно гравитации

Внимание! Для тестирования перемещения и ориентации, Вам нужно реальное iOs устройство подключенное к Вашему Macintosh через USB кабель. Симулятор и панель Canvas не определяют изменения в физическом перемещении эмулятора в пространстве.


Для того, чтобы использовать Core Motion в приложении, необходимо импортировать фреймворк CoreMotion и затем создать объект типа CMMotionManager:

import CoreMotion

let motionManager = CMMotionManager()

И необходимо создать очередь, куда будут приходить обновления с датчиков – OperationQueue. Без использования очереди, данные о положении и перемещении устройства могут поступать даже быстрее, чем приложение способно обработать физически, что приведет к подвисанию приложения или снижении быстродействия Вашего устройства. Определяется очередь как константа:

let queue = OperationQueue()

Акселерометр, iOs и  SwiftUI

Акселерометр может измерять сразу ускорение и силу тяжести в трех измерениях. Акселерометр может определять не только как iOs устройство держат в руках, но также и то, какой стороной(например лицевой или тыльной) устройство лежит на плоской поверхности(такой как например стол). Акселерометр измеряет g-силу(g– сила ускорения свободного падения), так значение 1.0 возвращается, если акселерометр измерил что сила 1g воздействует в конкретном направлении, как например:

  • Если устройство находит в идеальном портретном положении, будет получено значение равное силе 1g по оси y.
  • Если устройство держится под углом, то сила 1g будет равномерно распределена по осям, по которым идет смещение положения телефона от идеального. Если телефон держится под углом 45 градусов, сила 1g будет равномерно распределена между двумя осями.

Внезапное перемещение может быть отслежено акселерометром, если получаемые значения получаются значит больше, чем 1g. При нормальном использовании, акселерометр не получает значительно большие значения прикладываемой силы, чем 1g,  по одной или даже несколько осях, как указано на рисунке 10-1.

The iPhone accelerometer's axes in three dimensions
Рис 10-1. Оси акселерометра iPhone в 3-х проекциях. Ось X отвечает за перемещение влево и вправо, ось Y вверх и вниз и ось Z отвечает за приближение или отдаление устройства

Пришло время посмотреть как программно работать с акселерометром в SwiftUI за 12 шагов.

  1. Создадим новое iOs приложение и назовем его Chapter10AccelerateApp
  2. Откроем файл ContentView в панели Навигатора
  3. Добавим импорт библиотеки CoreMotion
    import CoreMotion
  4. Добавим объект, ответственный за получение данных с акселерометра, очередь для  приема данных и 3 State переменные для обозначения осей:
    let motionManager = CMMotionManager()
    let queue = OperationQueue()
        
    @State private var x: Double = 0.0
    @State private var y: Double = 0.0
    @State private var z: Double = 0.0
  5. В теле var body: some View удалим весь сгенерированный код и добавим VStack компонент с тремя виджетами Text
    VStack{
          Text("x: \(x)")
          Text("y: \(y)")
          Text("z: \(z)")
    }

    Эти 3 Text компонента отображает изменение значений, отвечающих за перемещение устройства по 3-м осям.

  6. В конце блока VStack добавим модификатор .onAppear со следующим кодом:
    .onAppear{
          motionManager.startAccelerometerUpdates(to: queue){
             (data: CMAccelerometerData?, error: Error?)in
                  guard let data = data else{
                      print("Error: \(error!)")
                      return
                  }
                  let trackMotion: CMAcceleration = data.acceleration
                  motionManager.accelerometerUpdateInterval = 2.5
                  DispatchQueue.main.async {
                     x = trackMotion.x
                     y = trackMotion.y
                     z = trackMotion.z
                  }
           }
    }

    .startAccelerometerUpdates проверяет, есть ли доступ к акселерометру. Если есть – функция получает обновления данных с датчиков  и сохраняет их в константе trackMotion. Для избежания проблем с зависанием мы используем GrandCentralDispatch в главном потоке – так как переменные x,y,z имеют доступ к UI интерфейсу.

    Для того, чтобы не нагружать процессор устройства слишком сильно, мы устанавливаем период получения обновлений с акселерометра раз в 2.5 секунды путем установления  .accelerometerUpdateInterval значения, равному 2.5, но Вы можете установить любое значение, какое Вам нужно. Исходный код ContentView выглядит так:

    import SwiftUI
    import CoreMotion
    
    struct ContentView: View {
        
        let motionManager = CMMotionManager()
        let queue = OperationQueue()
        
        @State private var x: Double = 0.0
        @State private var y: Double = 0.0
        @State private var z: Double = 0.0
    
        
        var body: some View {
            VStack{
                Text("x: \(x)")
                Text("y: \(y)")
                Text("z: \(z)")
            }.onAppear{
                motionManager.startAccelerometerUpdates(to: queue){
                    (data: CMAccelerometerData?, error: Error?)in
                    guard let data = data else{
                        print("Error: \(error!)")
                        return
                    }
                    let trackMotion: CMAcceleration = data.acceleration
                    motionManager.accelerometerUpdateInterval = 2.5
                    DispatchQueue.main.async {
                        x = trackMotion.x
                        y = trackMotion.y
                        z = trackMotion.z
                    }
                }
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

     

  7. Подсоедините iPhone к Вашему Mac через USB кабель, разблокируйте телефон и добавьте Ваш Mac в список доверенных компьютеров на iPhone.
  8. Выберите ProductDestination и выберите из списка девайсов Ваше iOs устройство, как показано на рисунке 10-2. Вы также можете нажать на текущее выбранное устройство, отображаемое в верхней части окна Xcode для отображения того же самого списка iOs устройств(см. Рисунок 10-3)
    Отслеживание движения ориентации SwiftUI
    Рис 10-2 Выбор актуального iOs устройства через меню Product

     

    Choosing an actual iOs device through a pop-up menu
    Рис 10-3 Выбор актуальных iOs устройств через всплывающее меню
  9. Выполните сборку и установку приложения на Ваше устройство, после того как оно откроется, поставьте Ваше iOs устройство на плоский стол. Значение z должно быть или 1 или -1. Как Вы Видите на рисунке 10-4, реальные значения не доходят до единицы по модулю. Если у Вас возникла ошибка при запуске приложения на iOs устройстве – “Ненадежный разработчик” – посмотрите здесь, как решить эту проблему.

    Accelerometer example SwiftUI
    Рис 10-4. Показания акселерометра для телефона, лежащего на плоском столе
  10. Возьмите смартфон в руки и постарайтесь держать его строго в портретном режиме. Показания y переменной будут 1 или -1(если быть точнее, то около этих значений)
  11. Поставьте телефон в ландшафтный режим. Значение x переменной будет в районе значений от 1 до -1.
  12. Нажмите на иконку Stop в среде XCode для остановки приложения. Исходный код Вы можете посмотреть и скачать также с нашего GitHub.

Отслеживание вращения с помощью гироскопа

Гироскоп отслеживает вращение и ориентацию вокруг осей x, y, z. Для начала посмотрите еще раз на рисунок 10-1. У нас то же направление осей, что и при работе с акселерометром. Вращение вокруг оси x возникает, когда мы наклоняем устройство вперед и назад относительно горизонтальной линии в центре устройства, проходящей параллельно земле. Вращение вокруг оси y возникает когда мы поворачиваем устройство относительно вертикальной линии, проходящей к центру земли через середину устройства. Вращение по оси z происходит когда мы вращаем устройство по часовой или против часовой стрелке через линию, проходящую через центр устройства, словно протыкающую девайс по направлении от нас к противоположной стороне.

Рассмотрим как работать с гироскопом в SwiftUI:

  1. Создадим новое iOs приложение и назовем его Chapter10GyroApp
  2. Откроем файл исходного кода ContentView в панели Навигатора
  3. Добавим импорт библиотеки CoreMotion
    import CoreMotion
    
  4. В структуре ContentView добавим 3 State переменные, отвечающие за оси в пространстве, а также очередь принимаемых данных с датчика и сам объект CMMotionManager для доступа к датчику – гироскопу
    let motionManager = CMMotionManager()
    let queue = OperationQueue()
        
    @State private var x: Double = 0.0
    @State private var y: Double = 0.0
    @State private var z: Double = 0.0
  5.  В теле UI структуры добавим контейнер VStack с тремя компонентами Text
    VStack{
         Text("x: \(x)")
         Text("y: \(y)")
         Text("z: \(z)")
    }

    Эти виджеты Text отображают изменение значений вращений устройства по осям x, y, z при воздействии пользователя на телефон

  6. Добавим модификатор .onAppear в конец блока VStack:
    .onAppear{
        motionManager.startGyroUpdates(to: queue) { (data : CMGyroData?, error : Error?) in
             guard let data = data else{
                  print("Error: \(error!)")
                  return
             }
             let trackMotion: CMRotationRate = data.rotationRate
             motionManager.gyroUpdateInterval = 2.5
             DispatchQueue.main.async {
                  x = trackMotion.x
                  y = trackMotion.y
                  z = trackMotion.z
    
             }
                    
        }
    }

    startGyroUpdates вначале проверяет доступ к гироскопу. Если доступен, то получает и сохраняет данные в константе trackMotion. Далее при помощи Grand Central Dispatch мы асинхронно обновляем значения наших State переменных в главном потоке. Мы взяли период получения обновления значений с гироскопа – раз в 2.5 секунды, что в целом довольно медленно. Сделали мы это для того, чтобы не напрягать вычислительные способности нашего устройства и не разряжать батарею чрезмерно.

    Исходный код нашего файла ContentView приведен ниже:

    //
    //  ContentView.swift
    //  Chapter10GyroApp
    //
    //  Created by Timur on 16.04.2023.
    //
    
    import SwiftUI
    import CoreMotion
    
    struct ContentView: View {
        
        let motionManager = CMMotionManager()
        let queue = OperationQueue()
        
        @State private var x: Double = 0.0
        @State private var y: Double = 0.0
        @State private var z: Double = 0.0
    
        var body: some View {
            VStack{
                Text("x: \(x)")
                Text("y: \(y)")
                Text("z: \(z)")
            }.onAppear{
                motionManager.startGyroUpdates(to: queue) { (data : CMGyroData?, error : Error?) in
                    guard let data = data else{
                        print("Error: \(error!)")
                        return
                    }
                    let trackMotion: CMRotationRate = data.rotationRate
                    motionManager.gyroUpdateInterval = 2.5
                    DispatchQueue.main.async {
                        x = trackMotion.x
                        y = trackMotion.y
                        z = trackMotion.z
    
                    }
                    
                }
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

     

  7. Подсоеденим наше iPhone устройство к Mac через USB кабель, а после разблокируем наше устройство
  8. Выбрав в среде XCode наше физическое устройство, куда мы будем устанавливать приложение в меню ProductDestination или в верхней панели устанавливаемых устройств(см. рисунок 10-2 и 10-3), установим наше приложение Chapter10GyroApp на физическое устройство.
  9. После того как приложение откроется, попробуйте быстро вращать устройство вокруг горизонтальной оси(ось x). Вы увидите на дисплее телефона, что значение переменной x резко меняется от -8 до 10.
  10. Точно также попробуйте быстро покрутить устройство относительно вертикальной оси. Теперь вы заметите, что значение y меняется с около нулевых значений до диапазона от 7 до -6.
  11. Вращайте ваше iOs устройство по часовой и против часовой стрелке вокруг z оси. Теперь Вы увидите, что значение z переменной изменяется до значений в диапазоне от 5 до -6.
  12. Нажмите на кнопку Stop в среде Xcode для остановки приложения

Измеряем магнитные поля

Магнитометр iOs устройства измеряет отношение магнитного поля Земли к iOs девайсу. Возвращаемые значения содержат измерение магнитного поля Земли в микротеслах, где значение x означает горизонтальное смещение к ближайшему магнитному полю, y значение содержит вертикальное смещение и z значение содержит измерение высоты относительно магнитного поля Земли.

Посмотрим, как получать данные с магнитометра:

  1. Создадим новое iOs приложение и дадим ему название Chapter10MagnetApp
  2. Откроем файл ContentView в панели Навигатора
  3. Добавим импорт фреймворка CoreMotion
    import CoreMotion
    
  4. Как обычно добавим 3 State переменные для отображения значений по 3-ом осям, а также очередь приема сообщений и сам объект CMMotionManager() для работы с магнитометром
    let motionManager = CMMotionManager()
    let queue = OperationQueue()
        
    @State private var x : Double = 0.0
    @State private var y : Double = 0.0
    @State private var z : Double = 0.0
  5. Удалим весь код, сгенерированный в теле структуры и добавим контейнер VStack() с тремя Text элементами:
    VStack {
         Text("x: \(x)")
         Text("y: \(y)")
         Text("z: \(z)")
    }

    Эти 3 Text компонента отображают изменение значений магнитных полей по 3-м осям.

  6. Добавим модификатор .onAppear в конец контейнера VStack:
    .onAppear{
         motionManager.startMagnetometerUpdates(to: queue){ (data: CMMagnetometerData?, error: Error?) in
             guard let data = data else{
                 print("Error: \(error!)")
                 return
             }
             let trackMotion: CMMagneticField = data.magneticField
             motionManager.magnetometerUpdateInterval = 1.0
             DispatchQueue.main.async {
                 x = trackMotion.x
                 y = trackMotion.y
                 z = trackMotion.z
             }       
        }
    }

    Метод .startMagnetometerUpdates проверяет, доступен ли магнитометр. Если доступен, получает данные с магнетометр и сохраняет их в константе trackMotion . Обновление переменных происходит асинхронно через Grand Central Dispatch в главном потоке

    Итоговый файл ContentView выглядит следующим образом:

    //
    //  ContentView.swift
    //  Chapter10MagnetApp
    //
    //  Created by Timur on 16.04.2023.
    //
    
    import SwiftUI
    import CoreMotion
    
    struct ContentView: View {
        
        let motionManager = CMMotionManager()
        let queue = OperationQueue()
        
        @State private var x : Double = 0.0
        @State private var y : Double = 0.0
        @State private var z : Double = 0.0
        
        var body: some View {
            VStack {
                Text("x: \(x)")
                Text("y: \(y)")
                Text("z: \(z)")
            }.onAppear{
                motionManager.startMagnetometerUpdates(to: queue){ (data: CMMagnetometerData?, error: Error?) in
                    guard let data = data else{
                        print("Error: \(error!)")
                        return
                    }
                    let trackMotion: CMMagneticField = data.magneticField
                    motionManager.magnetometerUpdateInterval = 1.0
                    DispatchQueue.main.async {
                        x = trackMotion.x
                        y = trackMotion.y
                        z = trackMotion.z
    
                    }
                    
                }
            }
            
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

     

  7. Подключите iPhone к Вашему Mac через USB кабель и разблокируйте Ваш iPhone.
  8. Выберите ProductDestination и выберите iOs устройство подсоединенное к Вашему Mac
  9. Походите и покрутите Ваш телефон, чтобы посмотреть как изменяются значения магнитных полей.
  10. Нажмите кнопку Stop в Xcode и остановите приложение

Обнаружение перемещения устройства

Под словом – перемещение устройства, мы обычно понимаем получение целой системы данных – крен, тангаж и рысканье. Для тех читателей, кто играл в воздушные симуляторы, это уже знакомые термины. Если кратко, то под креном подразумевается вращение устройства  вокруг вертикальной оси, под термином тангаж подразумевается вращение устройства вокруг горизонтальной оси и под рысканьем в таком случае идет речь вращение вокруг оси проходящей через центр устройства от наблюдателя к устройству. Вы можете посмотреть иллюстрацию этих понятий на рисунке 10-5.

Identifying roll, pitch and yaw on an iOs device
Рис 10-5 Схема, иллюстрирующая тангаж, крен и рысканье

Создадим простое iOs приложение чтобы разобраться на практике как получить значение крена, тангажа и рысканья на устройстве:

  1. Создадим новое iOs приложение и назовем его Chapter10DeviceMotionApp
  2. Откроем файл ContentView в панели Навигатора
  3. Импортируем библиотеку CoreMotion в наш проект:
    import CoreMotion
    
  4. Добавим 3 State переменные для отображения текущих значений крена, тангажа и рысканья. А также очередь для хранения поступающих данных с датчиков движения
    let motionManager = CMMotionManager()
    let queue = OperationQueue()
        
    @State private var pitch: Double = 0.0
    @State private var yaw: Double = 0.0
    @State private var roll: Double = 0.0
  5. Удалим все сгенерированные элементы UI и добавим контейнер VStack с 3-мя нам уже знакомыми компонентами Text
    VStack {
         Text("Pitch: \(pitch)")
         Text("Yaw: \(yaw)")
         Text("Roll: \(roll)")
    }

    Эти 3 компонента Text отображают текущие значение наклонов и поворотов устройства в различных направлениях.

  6. Добавим модификатор .onAppear в конец контейнера VStack:
    .onAppear{
         motionManager.startDeviceMotionUpdates(to: queue) {(data: CMDeviceMotion?, error: Error?) in
              guard let data = data else{
                  print("Error: \(error)")
                  return
              }
              let trackMotion : CMAttitude = data.attitude
              motionManager.deviceMotionUpdateInterval = 1.0
              DispatchQueue.main.async {
                  pitch = trackMotion.pitch
                  yaw = trackMotion.yaw
                  roll = trackMotion.roll
              }             
         }
    }

    startDeviceMotionUpdates проверяет, доступен ли сейчас акселерометр. Если доступен, мы получаем с него данные и сохраняем в константе trackMotion. Для того, чтобы наш UI не страдал от зависаний и подтормаживаний во время работы приложения, мы будем асинхронно обновлять наш UI через GrandCentralDispatch в главном потоке. Время обновления значений мы выбрали – 1 раз в 1 секунду. Исходный код файла ContentView выглядит следующим образом:

    //
    //  ContentView.swift
    //  Chapter10DeviceMotionApp
    //
    //  Created by Timur on 16.04.2023.
    //
    
    import SwiftUI
    import CoreMotion
    
    struct ContentView: View {
        
        let motionManager = CMMotionManager()
        let queue = OperationQueue()
        
        @State private var pitch: Double = 0.0
        @State private var yaw: Double = 0.0
        @State private var roll: Double = 0.0
    
        var body: some View {
            VStack {
                Text("Pitch: \(pitch)")
                Text("Yaw: \(yaw)")
                Text("Roll: \(roll)")
            }.onAppear{
                motionManager.startDeviceMotionUpdates(to: queue) {(data: CMDeviceMotion?, error: Error?) in
                    guard let data = data else{
                        print("Error: \(error)")
                        return
                    }
                    let trackMotion : CMAttitude = data.attitude
                    motionManager.deviceMotionUpdateInterval = 1.0
                    DispatchQueue.main.async {
                        pitch = trackMotion.pitch
                        yaw = trackMotion.yaw
                        roll = trackMotion.roll
                    }
                    
                }
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

     

  7. Подсоедините Ваш iPhone к Mac через USB кабель и разблокируйте Ваш iPhone
  8. В XCode выберите Ваше устройство как целовое для установки приложения.
  9. Покрутите Ваше устройство вокруг вертикальной оси. Значение Roll должно меняться от -2 до 3.
  10. Подбросьте вверх ваш девайс(не сильно). Значение тангажа будет менять от 1 до -2.
  11. Покрутите ваш девайс по часовой и против часовой стрелки. Значение рысканья будет менять от -2 до 1
  12. Нажмите на кнопку Stop в среде Xcode для остановки выполнения приложения.

Заключение

Каждое iOs устройство поставляется со встроенными датчиками для измерения перемещения. Для отслеживания перемещения любого iOs устройства, Вам необходимо использовать фреймворк CoreMotion.

iOs устройство в состоянии отслеживать: ускорение, вращение и даже магнитное поле Земли. Отслеживание перемещения iOs устройства позволяет приложению реагировать  должным образом на перемещение в пространстве и подходить к управлению и взаимодействию с приложением совсем другим образом.

Как мы убедились на наших примерах, работать с датчиками в среде iOs одно наслаждение – код предельно простой, лаконичный и понятный для программистов даже начального уровня.

Leave a Reply

Please disable your adblocker or whitelist this site!