Popover Controller на iPhone – пример создания из кода. ч.2

В первой части данного урока мы создали проект XCode, добавили файлы и классы для наших popover контроллеров, разместили контроллеры на Storyboard, добавили элементы интерфейса, с которыми будем работать и создали outlet’ы к ним. Т.е. сделали всю подготовительную работу — первые два пункта нашего плана.

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

Вернее, сначала мы все-таки «нарисуем»  вызов  popoverController  в storyboard, а затем напишем все что нам надо…

Перейдите в Interface Builder — т.е. кликните на файл Main.Storyboard.

fileList_popver_proSwift

 

В нем сделайте ctrl-перетягивание от кнопки «Зеленый» до PopVC контроллера. В открывшемся меню нужно выбрать пункт «Present As Popover»

ctrl_drag_popver_proSwift

Появится Segue со значком popver. Далее нужно выбрать этот Segue  и в инспекторе атрибутов задать ему идентификатор. Я установил «ppcs». Нужно запомнить этот идентификатор, потому что мы будем использовать его для определения перехода и презентации popover контроллера.

1stID_popver_proSwift

В класс ViewController сразу после метода didReciveMemoryWarning() добавьте следующий код:

//
// proSwift.ru
//

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // 1
        if segue.identifier == "ppcs" {
            // 2
            if let testPPVC = segue.destinationViewController as? PopVC{
                
                // 3
                if let ppc = testPPVC.popoverPresentationController {
                    ppc.delegate = self
                }
            }
        }
    }

При совершении перехода (Segue) происходит вызов метода prepareForSegue. Наша задача обработать именно наш переход. Для этого мы и будем использовать идентификатор, который вводили ранее.

  1. Проверяем идентификатор перехода и если он совпадает с нашим — выполняем дальнейший код.
  2. Получаем контроллер в который происходит переход с помощью свойства destinationViewController  и пытаемся привести его к классу PopVC. Если нам это удается, а нам это удается, потому что нет другого перехода с искомым в п.1 идентификатором, то выполняем пункт 3
  3. Проверяем может ли контроллер, в который мы переходим быть представленный как popover и если может, то устанавливаем в качестве делегата self т.е. ViewController (наш корневой контроллер с серым фоном)

В этот момент компилятор заругается на класс ViewController, т.к. пока мы его не объявили соответствующим протоколу делегата  UIPopoverPresentationControllerDelegate. Самое время это сделать.

Swift позволяет это сделать в качестве расширения (extension) для класса, и рекомендую делать это именно так для читабельности кода и сохранения его структуры.

В файл ViewController.swift ПОСЛЕ (НЕ ВНУТРИ) реализации класса нужно добавить следующий код:

//
// proSwift.ru
//

extension ViewController: UIPopoverPresentationControllerDelegate {
    func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
        return .None
    }
}

Тут мы сообщили компилятору,  что класс ViewController соответствует протоколу UIPopoverPresentationControllerDelegate и реализовали метод этого протокола adaptivePresentationStyleForPresentationController:

В данный момент можно запустить проект и посмотреть что получилось.

 green_popover_proswift

Наш контроллер заработал и показал зеленый popover. Пока не обращайте внимание на размеры контроллера и на точку, на которую указывает стрелка — в данном случае она указывает на точку с координатами (0,0) в системе координат view кнопки «Зеленый».

Давайте сделаем второй вариант вызова контроллера, используя код.

И для начала нужно задать идентификатор для голубого контроллера, чтобы получить ссылку на него из Storyboard.

2ndID_popver_proSwift

Я задал идентификатор «FromVTppc»

Далее нам потребуется обработка нажатия на наше blueView. Для этого будем использовать распознавание жестов.

Измените метод viewDidLoad класса ViewController, чтобы он выглядел следующим образом:

//
// proSwift.ru
//


    override func viewDidLoad() {
        super.viewDidLoad()
        
        let tap = UITapGestureRecognizer(target: self, action: "tapHandler:")
        blueView.addGestureRecognizer(tap)
      
    }

Мы создали экземпляр класса UITapGestureRecognizer, который у нашего класса ViewController (self) будет вызывать метод tapHandler:, в момент когда жест буде распознан. Как вы понимаете, жест этот — это тап.  Затем мы добавляем экземпляр этого класса на наше blueView.

Теперь нужно реализовать метод tapHandler:. Можно просто добавить этот метод в класс ViewController, но я опять рекомендую сделать расширение класса.

После расширения соответствия протоколу добавьте следующее расширение, в котором будет описан метод tapHandler:

//
// proSwift.ru
//

extension ViewController {
    func tapHandler(tap: UITapGestureRecognizer) {
        // 1
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        // 2
        let fvtppc = storyboard.instantiateViewControllerWithIdentifier("FromVTppc") as! FromViewTapPPC
        // 3
        fvtppc.modalPresentationStyle = .Popover
        
        // 4
        if let ppc = fvtppc.popoverPresentationController {
            // 5
            ppc.delegate = self
            // 6
            ppc.permittedArrowDirections = .Any
            // 7
            ppc.sourceView = tap.view
        }
        // 8
        presentViewController(fvtppc, animated: true, completion: nil)
    }
}

В нашем случае голубой контроллер создан в Storyboard и нам нужно показать его как popover.

  1. Получаем ссылку на наш Storyboard
  2. Получаем контроллер из storyboard с идентификатором «FromVTppc» и приводим его к классу FromViewTapPPC
  3. Полученному контроллеру задаем стиль модальной презентации popover
  4. Проверяем может ли полученный контроллер быть представленный как popover. и если может, то …
  5. Устанавливаем в качестве делегата self т.е ViewController
  6. Задаем разрешение показывать стрелки popover в любом направлении
  7. И обязательно указываем источник, на который эта стрелка будет показывать.
  8. Выводим на экран наш контроллер.

Класс ViewController уже соответствует протоколу  UIPopoverPresentationControllerDelegate, поэтому вопросов у компилятора не возникнет.

Запустите проект и понажимайте  на синее view

blue_popover_proswift

 

Оба контроллера работают, но стрелка голубого указывает немного неверно. Скоро мы и это поправим. Но чуть позже…

А сейчас давайте попробуем передать сообщение из нашего ViewController в FromViewTapPPC. Сделать это очень просто — все подготовительные работы мы проделали в первой части урока. Теперь надо установить нужное нам сообщение в свойство «name» экземпляра класса FromViewTapPPC. Добавьте следующую строку прямо перед presentViewController(fvtppc, animated: true, completion: nil)

 fvtppc.name = "Сообщение переданное из серого"

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

Запустите проект и нажмите  на синее view.

demo2_full_popver_proSwift

Все хорошо, вот только размер открывающихся контроллеров немного великоват. Но и это можно легко исправить.

Для начала исправим зеленый: в нем статическое сообщение и значит размер ему можно задать вполне определенный. Для этого в реализацию класса PopVC нужно добавить свойство preferredContentSize. Измените реализацию этого класса на следующую:

//
// proSwift.ru
//

import UIKit

class PopVC: UIViewController {
    
    override var preferredContentSize: CGSize {
        get {
            return CGSize(width: 360, height: 180)
        }
        set { super.preferredContentSize = newValue }
    }
 }

В данном случае мы задали вполне конкретный размер для зеленого popover. 360х180

Для синего контроллера ситуация немного другая — нужно определить размер содержимого в контроллере и исходя из этого размера вычислить размер самого контроллера. Замените реализацию класса на следующую:

//
// proSwift.ru
//

import UIKit

class FromViewTapPPC: UIViewController {

    @IBOutlet weak var label: UILabel!
    
    var name = "Программно сделаный popover" {
        didSet {
            label?.text = name
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        label.text = name
    }
    
    override var preferredContentSize: CGSize {
        get {
            if label != nil  && presentingViewController != nil {
                let size = label.sizeThatFits(presentingViewController!.view.bounds.size)
                let size20 = CGSize.init(width: size.width + 30, height: size.height + 60)
                return size20
            } else {
                return super.preferredContentSize
            }
            
        }
        
        set { super.preferredContentSize = newValue }
    }

    
}

Думаю, по коду все понятно. В size мы получили размер, в который вписывается label и для эстетичности добавили 30 по ширине и 60 по высоте.  Почему именно эти цифры? Они были получены методом подбора — я добавлял в размер различные значения и смотрел как будет красивее.

Запустите проект теперь.

sized_mes_g_b_popover_proswift

Последнее, с чем нам осталось разобраться это с местом, в которое указывает стрелочка popover контроллера. Как было сказано выше стрелочка зеленого контроллера указывает на точку с координатами (0.0) того view, которое его вызвало. Мы нарисовали вызов от кнопки «Зеленый» значит стрелка будет указывать в левый верхний угол этой кнопки.

Стрелка голубого указывает приблизительно туда же, и это в корне не правильно. Давайте поменяем стрелку для голубого контроллера. сразу после строки  ppc.sourceView = tap.view нужно добавить вот эту строку:

//
// proSwift.ru
//

ppc.sourceRect = CGRect(x: (tap.view?.bounds.size.width)! / 2, y: (tap.view?.bounds.size.height)! / 2, width: 1, height: 1)

В ней мы просим наш popover показывать стрелку на прямоугольник 1х1 в центре синего view. Можем запустить проект и посмотреть изменения:

demo6_sized_start_popver_proSwift

Можно было бы и так оставить проект, но можно сделать его еще более совершенным. Я имею ввиду, что мы можем указывать стрелку в то место синего view, куда тапнул пользователь. У нас для этого все есть, не нужно получать никаких дополнительных данных. Т.к. для обработки нажатия используется распознаватель жестов UITapGestureRecognizer координаты нажатия содержатся в нем. Нам нужно толко взять их и передать в  свойство  sourceRect.

Закоментируйте строку с sourceRect и вместо нее напишите вот этот код:

//
// proSwift.ru
//

let location = tap.locationInView(blueView)
ppc.sourceRect = CGRect(x: location.x, y: location.y, width: 1, height: 1)

После компиляции проекта голубой контроллер работает очень хорошо.

demo4_sized_popver_proSwift

На этом все, уважаемые читатели.

Ссылка на gitHub: https://github.com/Ironrnd/popoverVC

Также оставлю исходный код файлов проекта ниже по тексту.

ViewController.swift:

//
// proSwift.ru
//

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var blueView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let tap = UITapGestureRecognizer(target: self, action: "tapHandler:")
        blueView.addGestureRecognizer(tap)
        
        // 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.
    }

    
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "ppcs" {
            
            if let testPPVC = segue.destinationViewController as? PopVC{
                
                
                if let ppc = testPPVC.popoverPresentationController {
                    ppc.delegate = self
                }
               
            }

            
        }
    }

}

extension ViewController: UIPopoverPresentationControllerDelegate {
    func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
        return .None
    }
}

extension ViewController {
    func tapHandler(tap: UITapGestureRecognizer) {
        
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let fvtppc = storyboard.instantiateViewControllerWithIdentifier("FromVTppc") as! FromViewTapPPC
        fvtppc.modalPresentationStyle = .Popover
        
        
        if let ppc = fvtppc.popoverPresentationController {
            ppc.delegate = self
            ppc.permittedArrowDirections = .Any
            ppc.sourceView = tap.view
            
            //ppc.sourceRect = CGRect(x: (tap.view?.bounds.size.width)! / 2, y: (tap.view?.bounds.size.height)! / 2, width: 1, height: 1)
            
            let location = tap.locationInView(blueView)
            ppc.sourceRect = CGRect(x: location.x, y: location.y, width: 1, height: 1)
            
        }
        
        fvtppc.name = "Сообщение переданное из серого"
        
        presentViewController(fvtppc, animated: true, completion: nil)
        
    }
}

FromViewTapPPC.swift

import UIKit

class FromViewTapPPC: UIViewController {

    @IBOutlet weak var label: UILabel!
    
    var name = "Программно сделаный popover" {
        didSet {
            label?.text = name
        }
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        label.text = name
    }
    
    
    
    override var preferredContentSize: CGSize {
        get {
            if label != nil  && presentingViewController != nil {
                let size = label.sizeThatFits(presentingViewController!.view.bounds.size)
                let size20 = CGSize.init(width: size.width + 30, height: size.height + 60)
                return size20
            } else {
                return super.preferredContentSize
            }
            
        }
        
        set { super.preferredContentSize = newValue }
    }

    
}

PopVC.swift

import UIKit

class PopVC: UIViewController {
    
    override var preferredContentSize: CGSize {
        get {
            return CGSize(width: 360, height: 180)
            
        }
        
        set { super.preferredContentSize = newValue }
    }
    
}

 

5 Comments on “Popover Controller на iPhone – пример создания из кода. ч.2

  1. Спасибо автору. Хороший материал для новичков.
    Но вот название переменных и идентификаторов… меня если често начинает трясти, когда я вижу подобные названия в виде аббревиатур (fvtppc, size20 — почему 20), ppc)

    • Спасибо, Юрий!
      А по поводу имен — тут даже немного смешно вышло…
      fvtppc — это легко — при написании я имел ввиду From View Tap Popover Controller. А вот про size20 — пришлось лезть в код и вспоминать. И не получилось с ходу вспомнить! Но когда я залез в предыдущие версии проекта, то сразу вспомнил, что у меня там был CGRect 20х20 который в более поздних версиях поменял свой размер.

      Сокращенные названия я использовал, чтобы код помещался в том числе и на странице данного сайта.

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

      И вот еще проблема — я так и не смог победить Refractor в XCode со всеми его функциями, в.т.ч. изменение имен переменных. XCode мне говорит, что он может рефракторить только код Objective-C, а Swift ему не подвластен. Может кто сталкивался и решил проблему — подскажите.

  2. Я пониманию что статья не первой свежести. И все меняется. Но У меня не заработал ваш пример. Можете помочь разобраться ?
    Ошибка: No method declared with Objective-C selector ‘tapHandler:’
    Хотя функцию я описал как extention

    И второй момент это Method does not override any method from its superclass
    на воит эту функцию prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)

    помогите решить данные ошибки

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

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

*

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