Как применить перспективное преобразование к 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
, Проверьте это.
Вот некоторые изображения из поста:
Свифт 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
}