Avatar ·

UIActivityViewController вылетает на iPad в iOS 8: краш при показе

Всем привет! Я пишу приложение для iOS на Swift и столкнулся с проблемой: мой UIActivityViewController стабильно вылетает на iPad с iOS 8, хотя на iPhone всё работает отлично. Вот мой код: ```swift let textToShare = "Поделиться этим текстом" let activityVC = UIActivityViewController(activityItems: [textToShare], applicationActivities: nil) presentViewController(activityVC, animated: true, completion: nil) ``` На iPhone он показывает контроллер модально, и всё ок. Но на iPad (например, iPad Air с iOS 8.1) приложение падает с ошибкой: ``` *** Terminating app due to uncaught exception 'NSGenericException', reason: 'UIPopoverPresentationController should have a non-nil sourceView or barButtonItem set before the presentation occurs.' ``` Я читал, что на iPad UIActivityViewController отображается как popover, а не модально, и ему нужно указать источник — sourceView или barButtonItem. Но я не понимаю, как это правильно сделать в Swift. Пробовал добавить `activityVC.popoverPresentationController?.sourceView = self.view`, но всё равно вылетает. Где я ошибаюсь? И почему это происходит только на iOS 8? Помогите, пожалуйста, а то уже голову сломал. Заранее спасибо! <<>>

Проблема: iPad требует popover с источником

Вы абсолютно правы: на iPad (в отличие от iPhone) UIActivityViewController отображается не модально, а как popover. iOS 8 ввела UIPopoverPresentationController, и если не указать sourceView (или barButtonItem), система выбрасывает исключение. Ваш код почти правильный, но есть важный нюанс: sourceView нужно устанавливать до вызова presentViewController, а также обязательно задать sourceRect — область, откуда появится popover.

Правильное решение для iOS 8+ (Swift)

let textToShare = "Поделиться этим текстом"
let activityVC = UIActivityViewController(activityItems: [textToShare], applicationActivities: nil)

// Для iPad — настраиваем popover
if let popoverController = activityVC.popoverPresentationController {
    popoverController.sourceView = self.view // Ваша вьюха, откуда показываем
    popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0) // Центр экрана
    popoverController.permittedArrowDirections = .any // Разрешаем любую стрелку
}

presentViewController(activityVC, animated: true, completion: nil)

Почему это работает?

  • sourceView — указывает, к какой вьюхе привязан popover. Без него iOS не знает, откуда показывать.
  • sourceRect — координаты внутри sourceView, откуда вылетает popover. Если не задать, по умолчанию используется CGRect.zero, что может привести к странному поведению или крашу на некоторых устройствах.
  • permittedArrowDirections — направление стрелки. На iPad лучше оставить .any, чтобы система сама выбрала оптимальное.

Особенности iOS 8 и универсальный код

На iOS 8 popoverPresentationController доступен не у всех контроллеров, но у UIActivityViewController он есть. Чтобы код работал и на iPhone (где popover не нужен), проверяйте устройство:

if let popoverController = activityVC.popoverPresentationController,
   UIDevice.current.userInterfaceIdiom == .pad {
    popoverController.sourceView = self.view
    popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
}

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

Что делать, если краш всё равно происходит?

  • Убедитесь, что self.view не nil и уже добавлена в иерархию (например, вызывайте код в viewDidAppear, а не viewDidLoad).
  • Если показываете из кнопки, используйте sourceView = button и sourceRect = button.bounds — popover появится прямо от кнопки.
  • Проверьте, что не вызываете presentViewController дважды или из фонового потока.

Надеюсь, это поможет. Удачи с приложением!

<<>>

Альтернатива: использование barButtonItem для UIBarButtonItem

Если ваш UIActivityViewController вызывается из кнопки на панели навигации или тулбара, лучше использовать barButtonItem вместо sourceView. Это более естественно для iPad — popover привязывается к кнопке, а не к произвольной области экрана.

@IBAction func shareButtonTapped(_ sender: UIBarButtonItem) {
    let textToShare = "Поделиться"
    let activityVC = UIActivityViewController(activityItems: [textToShare], applicationActivities: nil)
    
    // На iPad привязываем к barButtonItem
    if let popoverController = activityVC.popoverPresentationController,
       UIDevice.current.userInterfaceIdiom == .pad {
        popoverController.barButtonItem = sender
    }
    
    present(activityVC, animated: true, completion: nil)
}

Преимущества этого подхода

  • Не нужно вычислять sourceRect — iOS сама определит, откуда показывать popover.
  • Код чище и короче.
  • Работает на всех версиях iOS, начиная с 8.0.

Этот способ особенно удобен, когда кнопка находится в навигационной панели или тулбаре. Попробуйте — он часто решает проблему с крашем на iPad без лишних телодвижений.