Airdrop, UIActivityViewController или как сняь и отправить фото

Задача

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

Интерфейс будет не сложным, в центре будет UIImageView, которая будет отражать выбранную картинку, и три кнопки — захват фото с камеры, выбор фото из галереи, и кнопка отправить.

В конце урока должно получиться такое приложение:

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

Откройте Xcode, создайте новый проект, назовите его Airdrop. Перейдите в Storyboard и из библиотеки элементов перетащите на ViewController  ImageView и три кнопки UIButton. Распределите элементы так как вам будет удобно, и задайте констрейнты для определения положения элементов относительно друг друга. У меня получилось так:

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

//
// proSwift.ru
//

    @IBOutlet weak var pictureView: UIImageView!

    @IBOutlet weak var camBtn: UIButton!
    @IBOutlet weak var galBtn: UIButton!
    @IBOutlet weak var sendBtn: UIButton!

    @IBAction func camButtonTapped(_ sender: UIButton) {
    }
    
    @IBAction func galButtonTapped(_ sender: UIButton) {
    }
 
    @IBAction func sendButtonTapped(_ sender: UIButton) {
    }

 

Для визуализации кнопок давайте в класс ViewController добавим вспомогательный метод конфигурирования:

//
// proSwift.ru
//

    private func configureButton(btn: UIButton) {
        btn.backgroundColor = UIColor(red: 27/255, green: 89/255, blue: 147/255, alpha: 1.0)
        btn.setTitleColor(UIColor.white, for: .normal)
        btn.setTitleColor(UIColor.lightGray, for: .highlighted)
        
        btn.layer.cornerRadius = 4.0
        
        btn.layer.shadowOpacity = 0.6
        btn.layer.shadowOffset = CGSize(width: 2, height: 2)
        btn.layer.shadowRadius = 2
    }

Данный метод добавляет закругление углов, цвет фона, тень и цвет надписи. Мы такое уже много раз делали. Можете почитать предыдущие статьи на сайте.  Также в проект следует добавить любую картинку — для отображения при запуске и изменить метод viewDidLoad() для применения вышенаписанного метода к кнопкам и отображения этой стартовой картинки.

//
// proSwift.ru
//

    override func viewDidLoad() {
        super.viewDidLoad()
        configureButton(btn: camBtn)
        configureButton(btn: galBtn)
        configureButton(btn: sendBtn)
        
        pictureView.image = UIImage(named: "proSwift_logo.jpg")
        
    }

Скомпилируйте и запустите проект. Мне еще пришлось совсем немного подкорректировать ширину кнопок и констрейнты для них, но думаю вы самостоятельно разберетесь. У меня проект после компиляции выглядит так:

 

Фото с камеры iPhone

Для начала получим фото с камеры iPhone. Думаю, понятно что тестировать этот кусок программы нужно на реальном устройстве, а не на симуляторе.  Итак, первое что нужно сделать — это запросить разрешения у пользователя для доступа к камере.  Хочу немного отвлечься — при написании этого приложения я не запрашивал разрешения доступа, однако камера нормально работала и передавала изображения. Политика Apple требует запроса этого разрешения и нам лучше его сделать. А для этого перейдите в файл info.plist, добавьте строку и впишите в столбец ключ Privacy — Camera Usage Description. Значение этого ключа — это будет то сообщение которое увидит пользователь при запросе этого разрешения. Мы это чуть позже увидим.

Далее перейдите во ViewController и измените метод camButtonTapped(:)

//
// proSwift.ru
//

    @IBAction func camButtonTapped(_ sender: UIButton) {
        let imagePicker = UIImagePickerController() // 1
        imagePicker.delegate = self // 2
        imagePicker.sourceType = UIImagePickerControllerSourceType.camera // 3
        
        // для выбора только фотокамеры, не для записи видео
        imagePicker.showsCameraControls = true // 4
        
        self.present(imagePicker, animated: true, completion: nil) // 5
    }

Разберем по строкам:

  1. Создаем экземпляр UIImagePickerController и присваиваем его переменной  imagePicker
  2. Устанавливаем себя, то есть ViewController в качестве делегата imagePicker. На данной строке компилятор выдаст ошибку, т.к. пока наш ViewController не соответсвует требуемым протоколам, но мы это исправим чуть позже.
  3. Тут все понятно — тип источника — камера устройства
  4. Включаем показ кнопок управления камерой
  5. Выводим imagePicker на экран

Как и было сказано выше, нужно добавить соответствие ViewController протоколам делегата UIPickerViewController. Данный делегат требует два протокола, поэтому давайте их и добавим. Сразу после декларации класса ViewController добавьте расширение, т.е. следующий код:

//
// proSwift.ru
//

extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    // UIImagePickerControllerDelegate
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        let imageFromPC = info[UIImagePickerControllerOriginalImage] as! UIImage // 1
        pictureView.image = imageFromPC // 2
        self.dismiss(animated: true, completion: nil) // 3
    }
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        self.dismiss(animated: true, completion: nil)
    }
}

Внимательно посмотрите на этот кусок кода. Нужно понимать, что мы определили ViewController как соответствующий UINavigationControllerDelegate,  но не использовали ни одного метода этого протокола. Для текущей задачи они не нужны, однако опустить этот протокол нельзя, т.к. уже говорилось выше, он требуется для делегата imagePicker.

А вот два метода, которые мы используем — являются методами протокола UIImagePickerControllerDelegate. Первый метод срабатывает, когда закончился процесс получения медиа-данных (в нашем случае фотографии). В нем мы:

  1. Из данных, которые вернул  imagePicker извлекли картинку
  2. Полученную картинку установили в pictureView
  3. Убрали с экрана imagePicker

Второй метод вызывается в момент отказа пользователя от выбора картинки. В этом случае мы просто убираем imagePicker с экрана и все.

Ошибка компилятора при присвоении делегата должна исчезнуть. Можно запускать и тестировать приложение.

Нажатие на кнопку Камера привет к такому результату:

Вы видите запрос с текстом, который мы указывали в файле info.plist. Все вполне логично.

Даем разрешение, делаем фото…

.. жмем кнопку use Foto — окно камеры закроется и мы увидим нашу фотографию месте стартовой картинки.




Фото из галереи iPhone

Начать нужно с очередного запроса разрешения. На этот раз разрешения доступа к галереи устройства. Для этого в файл info.plist, добавьте строку и впишите в столбец ключ Privacy — Photo Library Usage Description. Как и в ситуации с камерой, значение этого ключа является сообщением, которое увидит пользователь при запросе этого разрешения.

А что касается кода, то он буде еще проще, чем при варианте с камерой. Добавьте эти строки в метод galButtonTapped(:)

 

//
// proSwift.ru
//

    @IBAction func galButtonTapped(_ sender: UIButton) {

        let imagePicker = UIImagePickerController() // 1
        imagePicker.delegate = self // 2
        self.present(imagePicker, animated: true, completion: nil) // 3
        
    }

Как видите, код идентичный. Просто при создании экземпляра UIImagePickerController мы явно не указываем тип источника. По умолчанию этот источник — галерея. Методы делегата все те же самые, поэтому эти три строки — это все что нам требуется чтобы открыть выбор фотографии из галереи.

Компилируем и тестируем что получилось. По кнопке Галерея видим запрос на доступ к фотографиям устройства:

Выбираем фотографию, окно с галереей закроется и мы увидим выбранную фотографию в pictureView.

Airdrop и прочий share…

Для выполнения нужного функционала нам нужен UIActivityViewController. Изменим метод sendButtonTapped(:)

//
// proSwift.ru
//

    @IBAction func sendButtonTapped(_ sender: UIButton) {
        let activityItems = [pictureView.image!] // 1
        let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) // 2
        self.present(activityController, animated: true, completion: nil)
    }

Мы создаем экземпляр UIActivityViewController и при его инициализации (2) передаем МАССИВ! с элементами. В нашем случае это картинка, она одна, и поэтому это будет массив с одним элементом (1).  Ну и выводим на экран созданный контроллер (3).

Уважаемые читатели, это все что нужно. Все остальное за нас сделает iOS. Отображение элементов нашего share-контроллера будет получено из текущих настроек iOS. Скомпилируйте приложение и посмотрите что получится.

Привожу полный код ViewController

//
// proSwift.ru
//

import UIKit

class ViewController: UIViewController {

    
    @IBOutlet weak var pictureView: UIImageView!
    
    @IBOutlet weak var camBtn: UIButton!
    @IBOutlet weak var galBtn: UIButton!
    @IBOutlet weak var sendBtn: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureButton(btn: camBtn)
        configureButton(btn: galBtn)
        configureButton(btn: sendBtn)
        
        pictureView.image = UIImage(named: "proSwift_logo.jpg")
        
    }

    @IBAction func camButtonTapped(_ sender: UIButton) {
        let imagePicker = UIImagePickerController() // 1
        imagePicker.delegate = self // 2
        imagePicker.sourceType = UIImagePickerControllerSourceType.camera // 3
        
        // для выбора только фотокамеры, не для записи видео
        imagePicker.showsCameraControls = true // 4
        
        self.present(imagePicker, animated: true, completion: nil) // 5
    }
    
    @IBAction func galButtonTapped(_ sender: UIButton) {

        let imagePicker = UIImagePickerController() // 1
        imagePicker.delegate = self // 2
        self.present(imagePicker, animated: true, completion: nil) // 3
        
    }
 
    @IBAction func sendButtonTapped(_ sender: UIButton) {
        let activityItems = [pictureView.image!]
        let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
        self.present(activityController, animated: true, completion: nil)
    }

    private func configureButton(btn: UIButton) {
        btn.backgroundColor = UIColor(red: 27/255, green: 89/255, blue: 147/255, alpha: 1.0)
        btn.setTitleColor(UIColor.white, for: .normal)
        btn.setTitleColor(UIColor.lightGray, for: .highlighted)
        
        btn.layer.cornerRadius = 4.0
        
        btn.layer.shadowOpacity = 0.6
        btn.layer.shadowOffset = CGSize(width: 2, height: 2)
        btn.layer.shadowRadius = 2
    }
}


extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    // UIImagePickerControllerDelegate
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        let imageFromPC = info[UIImagePickerControllerOriginalImage] as! UIImage // 1
        pictureView.image = imageFromPC // 2
        self.dismiss(animated: true, completion: nil) // 3
    }
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        self.dismiss(animated: true, completion: nil)
    }
}

Ссылка проекта на GitHub



3 Comments on “Airdrop, UIActivityViewController или как сняь и отправить фото

  1. func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    let imageFromPC = info[UIImagePickerController.InfoKey.originalImage] as! UIImage // 1

    • Спасибо за поправку! А то я всю голову сломал. Делал по инструкции, но ничего не отображалось, а стоило внести эту поправку и сразу все заработало. Xcode 11.2.1 (11B500)

Добавить комментарий

Ваш адрес email не будет опубликован.

*

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.