Как применить перспективное преобразование к UIView?

199

Я ищу, чтобы выполнить перспективное преобразование на UIView (например, видно в coverflow)

Кто-нибудь знает, возможно ли это?

Я исследовал использование CALayerи пробежался по всем прагматичным подкастам Core Animation для программиста, но до сих пор не знаю, как создать такой вид трансформации на iPhone.

Любая помощь, указатели или примеры фрагментов кода будут очень благодарны!

Ник Картрайт
источник
Я не уверен, подходит ли вам это или нет, но когда я делаю анимацию, я обнаружил, что m14 лучше для меня. Просто для справки :)
b123400
Спасибо! - какие ценности вы это даете?
Ник Картрайт
1
Установив m14, вы на самом деле странно искажаете область видимости по оси X. Математика совершенно верна, но в общем случае она может привести к путанице.
пушистый
Очень хорошая статья о 3D-преобразовании и перспективе CALayer, включая подробное объяснение поля m34, можно найти в этой превосходной статье: core-animation-3d-model
Маркус

Ответы:

329

Как сказал Бен, вам нужно работать со UIView'sслоем, используя CATransform3Dдля выполнения layer's rotation. Хитрость , чтобы получить перспективную работу, так как описано здесь , является непосредственно доступ к одному из матрицы cellsиз CATransform3D(m34). Математическая математика никогда не была моей вещью, поэтому я не могу точно объяснить, почему это работает, но это работает. Вам нужно будет установить это значение в отрицательную дробь для вашего первоначального преобразования, а затем применить к нему преобразования вращения слоя. Вы также должны быть в состоянии сделать следующее:

Objective-C

UIView *myView = [[self subviews] objectAtIndex:0];
CALayer *layer = myView.layer;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / -500;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 45.0f * M_PI / 180.0f, 0.0f, 1.0f, 0.0f);
layer.transform = rotationAndPerspectiveTransform;

Swift 5.0

if let myView = self.subviews.first {
    let layer = myView.layer
    var rotationAndPerspectiveTransform = CATransform3DIdentity
    rotationAndPerspectiveTransform.m34 = 1.0 / -500
    rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 45.0 * .pi / 180.0, 0.0, 1.0, 0.0)
    layer.transform = rotationAndPerspectiveTransform
}

который восстанавливает слой преобразования с нуля для каждого поворота.

Полный пример этого (с кодом) можно найти здесь , где я реализовал ротацию на основе касания и масштабирование для пары на CALayersоснове примера Билла Дадни. В самой последней версии программы, в самом низу страницы, реализована такая перспективная операция. Код должен быть достаточно простым для чтения.

sublayerTransformВы ссылаетесь в вашем ответе есть преобразование , которое применяется к подслоя из ваших UIView's CALayer. Если у вас нет подслоев, не беспокойтесь об этом. Я использую sublayerTransform в моем примере просто потому, что CALayersв одном слое, который я вращаю, содержатся два элемента.

Брэд Ларсон
источник
1
Спасибо, Брэд - ты звезда. PS: извините за позднюю пометку вашего отличного ответа! Ник.
Ник Картрайт
Это хорошо работает для меня, за исключением того, что я, кажется, теряю половину своего изображения (вдоль вертикального деления) - иногда LHS, иногда RHS.
iPadDeveloper2011
18
@ iPadDeveloper2011 - Скорее всего, вы пытаетесь повернуть вид или слой, когда на той же плоскости есть другой непрозрачный вид или слой. Половина вашего вида или слоя, который выступает за пределы экрана, будет ниже этого другого вида и, таким образом, будет скрыта. Вы можете изменить порядок иерархии представлений, чтобы предотвратить это, или использовать свойство zPosition, чтобы переместить ваше представление переднего плана достаточно высоко над тем, которое его обрезает.
Брэд Ларсон
26
Кстати, причина, по которой ячейка m34 влияет на перспективное преобразование, заключается в том, что именно эта ячейка в матрице влияет на то, как значение Z в мировом пространстве отображается на значение W в пространстве клипа (которое используется для проекции в перспективе). Читайте о матрицах однородного преобразования для получения дополнительной информации.
пушистый
2
@uerceg - Вы захотите задать этот вопрос в виде отдельного вопроса, желательно с изображениями, которые иллюстрируют, что происходит и что вы хотите, чтобы произошло.
Брэд Ларсон
7

Вы можете использовать только преобразования Core Graphics (Quartz, 2D), непосредственно примененные к свойству transform UIView. Чтобы получить эффекты в coverflow, вы должны будете использовать CATransform3D, которые применяются в трехмерном пространстве, и, следовательно, могут дать вам перспективный вид, который вы хотите. Вы можете применять CATransform3D только к слоям, а не к видам, поэтому для этого вам придется переключиться на слои.

Проверьте образец "CovertFlow", который идет с XCode. Это только для Mac (т.е. не для iPhone), но многие концепции хорошо переносятся.

Бен Готлиб
источник
Я ищу этот пример покрытия потока в / Developer / examples, но безуспешно, где его найти?
Alex
На самом деле это "CovertFlow", и он находится в / Developer / examples / Quartz / Core Animation / "
Бен Готлиб
3

Чтобы добавить ответ Брэда и привести конкретный пример, я заимствую свой ответ в другом посте на аналогичную тему. В своем ответе я создал простую игровую площадку Swift, с которой вы можете скачать и поиграть , где я демонстрирую, как создавать перспективные эффекты UIView с простой иерархией слоев, и показываю разные результаты между использованием transformи sublayerTransform. Проверьте это.

Вот некоторые изображения из поста:

Опция .sublayerTransform

опция .transform

HuaTham
источник
Код должен быть опубликован в виде текста. Действительно трудно читать код на изображении. Кроме того, код на картинке нельзя ссылаться, копировать или искать.
rmaddy
@ rmaddy, у меня есть код, доступный по ссылке Bitbucket в моем ответе.
HuaTham
Ссылки со временем портятся. Всегда лучше вставить важный код в виде текста прямо в ответ.
rmaddy
-1

Вы можете получить точный эффект Карусели, используя iCarousel SDK.

Вы можете получить мгновенный эффект Cover Flow на iOS с помощью великолепной и бесплатной библиотеки iCarousel. Вы можете скачать его с https://github.com/nicklockwood/iCarousel и довольно легко добавить его в свой проект Xcode, добавив соединительный заголовок (он написан на Objective-C).

Если вы ранее не добавляли код Objective-C в проект Swift, выполните следующие действия:

  • Скачайте iCarousel и распакуйте его
  • Перейдите в разархивированную папку, откройте ее подпапку iCarousel, затем выберите iCarousel.h и iCarousel.m и перетащите их в навигацию проекта - это левая панель в Xcode. Чуть ниже Info.plist в порядке.
  • Установите флажок «Копировать элементы при необходимости», затем нажмите «Готово».
  • Xcode предложит вам сообщение «Хотите настроить заголовок моста Objective C?» Нажмите «Создать заголовок моста». В вашем проекте вы увидите новый файл с именем YourProjectName-Bridging-Header.h.
  • Добавьте эту строку в файл: #import "iCarousel.h"
  • Как только вы добавили iCarousel в свой проект, вы можете начать его использовать.
  • Убедитесь, что вы соответствуете протоколам iCarouselDelegate и iCarouselDataSource.

Образец кода Swift 3:

    override func viewDidLoad() {
      super.viewDidLoad()
      let carousel = iCarousel(frame: CGRect(x: 0, y: 0, width: 300, height: 200))
      carousel.dataSource = self
      carousel.type = .coverFlow
      view.addSubview(carousel) 
    }

   func numberOfItems(in carousel: iCarousel) -> Int {
        return 10
    }

    func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
        let imageView: UIImageView

        if view != nil {
            imageView = view as! UIImageView
        } else {
            imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 128, height: 128))
        }

        imageView.image = UIImage(named: "example")

        return imageView
    }
Технология
источник