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

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

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

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

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

5 ответов

Решение

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

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;

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

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

Подслой Transform, на который вы ссылаетесь в своем ответе, является преобразованием, которое применяется к подуровням вашего UIView. CALayer, Если у вас нет подслоев, не беспокойтесь об этом. Я использую sublayerTransform в моем примере просто потому, что в одном слое, который я вращаю, содержатся два слоя CALay.

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

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

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

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

Опция.sublayerTransform

опция.transform

Свифт 5.0

      func makeTransform(horizontalDegree: CGFloat, verticalDegree: CGFloat, maxVertical: CGFloat,rotateDegree: CGFloat, maxHorizontal: CGFloat) -> CATransform3D {
    var transform = CATransform3DIdentity
           
    transform.m34 = 1 / -500
    
    let xAnchor = (horizontalDegree / (2 * maxHorizontal)) + 0.5
    let yAnchor = (verticalDegree / (-2 * maxVertical)) + 0.5
    let anchor = CGPoint(x: xAnchor, y: yAnchor)
    
    setAnchorPoint(anchorPoint: anchor, forView: self.imgView)
    let hDegree  = (CGFloat(horizontalDegree) * .pi)  / 180
    let vDegree  = (CGFloat(verticalDegree) * .pi)  / 180
    let rDegree  = (CGFloat(rotateDegree) * .pi)  / 180
    transform = CATransform3DRotate(transform, vDegree , 1, 0, 0)
    transform = CATransform3DRotate(transform, hDegree , 0, 1, 0)
    transform = CATransform3DRotate(transform, rDegree , 0, 0, 1)
    
    return transform
}

func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x, y: view.bounds.size.height * anchorPoint.y)
    var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x, y: view.bounds.size.height * view.layer.anchorPoint.y)
    
    newPoint = newPoint.applying(view.transform)
    oldPoint = oldPoint.applying(view.transform)
    
    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x
    
    position.y -= oldPoint.y
    position.y += newPoint.y
    
    print("Anchor: \(anchorPoint)")
    
    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}

вам нужно только вызвать функцию с вашей степенью. Например:

      var transform = makeTransform(horizontalDegree: 20.0 , verticalDegree: 25.0, maxVertical: 25, rotateDegree: 20, maxHorizontal: 25)
imgView.layer.transform = transform

Вы можете получить точный эффект Карусели, используя 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
    }
Другие вопросы по тегам