Как сделать пропорционально масштабируемый слой CALayer и все его подслои?

Я пытаюсь создать NSScrollView, который отображает страницы документов. Я добавил NSView с поддержкой CALayer в качестве documentView NSScrollView, а затем добавил подуровень CALayer в documentView. Когда я увеличиваю масштаб NSScollview, documentView правильно увеличивается и уменьшается. Однако подслои documentView не масштабируются пропорционально их содержанию documentView. Если я не устанавливаю autoresizingMask на подслоях слоя documentView, подслои просто вылетают за пределы экрана при масштабировании documentView. Если я использую параметры LayerWidthSizable / LayerHeightSizable, подслои становятся намного больше или меньше, чем они должны быть относительно суперуровня documentView. Вот код, который у меня есть:

Вот NSScrollview:

class LayerScrollView: NSScrollView {

    var containerLayer: ContainerLayer!

    override func awakeFromNib() {
        documentView = ContainerLayer(frame: frame)
    }
}

Вот ContainerLayer (documentView CALayer):

class ContainerLayer: NSView {

    let documentLayer: DocumentLayer = DocumentLayer()

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        autoresizesSubviews = true
        wantsLayer = true
        layer = CATiledLayer()
        layer?.delegate = self
        layer?.backgroundColor = NSColor.blueColor().CGColor
        layer?.masksToBounds = true
        documentLayer.frame = CGRect(x: frame.width / 4.0, y: frame.height / 4.0, width: frame.width / 2.0, height: frame.height / 2.0)
        documentLayer.delegate = documentLayer
        layer?.addSublayer(documentLayer)
        documentLayer.autoresizingMask = CAAutoresizingMask.LayerWidthSizable | CAAutoresizingMask.LayerHeightSizable
        documentLayer.setNeedsDisplay()
    }

    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func drawLayer(layer: CALayer!, inContext ctx: CGContext!) {
        CGContextSetFillColorWithColor(ctx, NSColor.redColor().CGColor)
        CGContextFillRect(ctx, layer.bounds)
    }
}

И, наконец, вот DocumentLayer (подслои, содержащиеся в DocumentLayer):

class DocumentLayer: CALayer {

    override func drawLayer(layer: CALayer!, inContext ctx: CGContext!) {
        CGContextSetFillColorWithColor(ctx, NSColor.redColor().CGColor)
        CGContextFillRect(ctx, layer.bounds)
    }
}

Вот изображение, иллюстрирующее проблему и мои желаемые результаты:

Синий прямоугольник - это ContainerLayer, красный прямоугольник - это DocumentLayer.

Я просмотрел множество руководств и документов и ничего не нашел. Похоже, это должно быть очень просто. Что мне здесь не хватает?


person Ideasthete    schedule 08.09.2014    source источник
comment
Похоже, преобразование масштабирования применяется дважды. Вы пробовали использовать не CATiledLayer, а обычный CALayer? Вы проверяли, что происходит, когда вы устанавливаете границы вместо documentLayer.frame? Кроме того, чтобы помочь отладке, используйте borderWidth и borderColor, чтобы вы могли видеть, каковы на самом деле границы слоя, а поведение не исходит от drawLayer. Также, чтобы разобраться в проблеме, вы можете зарегистрировать свойство transform.   -  person mahal tertin    schedule 10.09.2014
comment
Спасибо @mahaltertin. Как оказалось, Apple рекомендует не устанавливать фрейм или границы напрямую для слоев, которые находятся внутри представлений, управляемых Auto Layout, и решение состоит в использовании ограничений, но использование свойства borderWidth было очень полезно при отладке.   -  person Ideasthete    schedule 11.09.2014


Ответы (1)


ОБНОВЛЕНИЕ: РЕШЕНО

Вот решение, найденное после нескольких часов копания в документации и других материалах (также спасибо @mahaltertin за предложение использовать borderWidth для отладки):

LayerScrollView:

class LayerScrollView: NSScrollView {

    var containerLayer: ContainerLayer!

    override func awakeFromNib() {
        containerLayer = ContainerLayer(frame: frame)
        documentView = containerLayer
    }
}

Контейнерослой:

class ContainerLayer: NSView {

    let documentLayer: DocumentLayer = DocumentLayer()

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        wantsLayer = true
        layer = CALayer()
        layer?.layoutManager = CAConstraintLayoutManager.layoutManager()
        layer?.delegate = self
        layer?.backgroundColor = NSColor.blueColor().CGColor

        documentLayer.delegate = documentLayer
        documentLayer.name = "documentLayer"
        documentLayer.borderWidth = 1.0
        documentLayer.backgroundColor = NSColor.redColor().CGColor

        documentLayer.addConstraint(CAConstraint(attribute: CAConstraintAttribute.Width, relativeTo: "superlayer", attribute: CAConstraintAttribute.Width, scale: 0.5, offset: 0.0))
        documentLayer.addConstraint(CAConstraint(attribute: CAConstraintAttribute.Height, relativeTo: "superlayer", attribute: CAConstraintAttribute.Height, scale: 0.5, offset: 0.0))
        documentLayer.addConstraint(CAConstraint(attribute: CAConstraintAttribute.MidX, relativeTo: "superlayer", attribute: CAConstraintAttribute.MidX, scale: 1.0, offset: 0.0))
        documentLayer.addConstraint(CAConstraint(attribute: CAConstraintAttribute.MidY, relativeTo: "superlayer", attribute: CAConstraintAttribute.MidY, scale: 1.0, offset: 0.0))

        layer?.addSublayer(documentLayer)

        layer?.setNeedsDisplay()
        documentLayer.setNeedsDisplay()
    }

    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

И DocumentLayer:

class DocumentLayer: CALayer {

    override func actionForKey(event: String!) -> CAAction! {
        return nil
    }
}

Решение - использовать ограничения. Переопределение метода actionForKey в DocumentLayer состоит в том, чтобы предотвратить анимацию DocumentLayer во время масштабирования. Ограничения, определенные в ContainerLayer, указывают, что DocumentLayer должен быть половиной ширины / высоты ContainerLayer и что середина обоих слоев должна быть одинаковой. Теперь масштабирование сохраняет правильные пропорции.

person Ideasthete    schedule 10.09.2014
comment
Вы импортировали какую-нибудь библиотеку? Потому что у меня проблема: использование неразрешенного идентификатора CAConstraint. - person Carolina; 10.01.2017
comment
Нет - прошло некоторое время с тех пор, как я опубликовал это, поэтому вполне вероятно, что обновление где-то изменило название. - person Ideasthete; 10.01.2017
comment
@Carol, сегодня я получил ту же ошибку и обнаружил, что CAConstraint доступен только в macOS, но не в iOS. developer.apple.com/documentation/quartzcore/calayer/ - person dfd; 23.04.2018