Размер CALayers не изменился при изменении границ UIView. Зачем?

101

У меня UIViewесть около 8 различных CALayerподслоев, добавленных к его слою. Если я изменяю границы представления (анимированные), то само представление сжимается (я проверял это с помощью a backgroundColor), но размер подслоев остается неизменным .

Как это решить?

Гери Борбас
источник

Ответы:

149

Я использовал тот же подход, что и Солин, но в этом коде есть опечатка. Метод должен быть:

- (void)layoutSubviews {
  [super layoutSubviews];
  // resize your layers based on the view's new bounds
  mylayer.frame = self.bounds;
}

Для моих целей я всегда хотел, чтобы подслой был во весь размер родительского представления. Поместите этот метод в свой класс представления.

Чедвик Вуд
источник
8
@fishinear ты уверен? похоже, что вы описываете фрейм. границы теоретически всегда должны иметь начало 0,0.
griotspeak
3
@griotspeak - на самом деле это не так. См. Описание свойства bounds здесь: developer.apple.com/library/ios/#documentation/uikit/reference/… - "По умолчанию исходная точка прямоугольника границ установлена ​​на (0, 0), но вы можете измените это значение, чтобы отображать различные части представления ".
jdc
Большое спасибо, с тех пор я узнал о нескольких случаях (например, в режиме прокрутки), когда это неправда, но я забыл об этом комментарии.
griotspeak
31
Не забудьте вызвать [super layoutSubviews], так как это может вызвать неожиданное поведение в различных классах UIKit.
Kpmurphy91
2
Для меня это не очень хорошо сработало с саморазмером UITextView(scrollEnabled = NO) и автоматическим макетом. У меня было текстовое представление, прикрепленное к его супервизору, которое, в свою очередь, имело CALayerнижнюю часть (границу), и я хотел, чтобы оно обновляло положение границы при изменении высоты текстового представления. По какой-то причине layoutSubiewsбыл вызван слишком поздно (и положение слоя было анимировано).
iosdude
26

Поскольку CALayer на iPhone не поддерживает менеджеры компоновки, я думаю, вам нужно сделать основной слой вашего представления настраиваемым подклассом CALayer, в котором вы переопределяете layoutSublayersустановку кадров всех подслоев. Вы также должны переопределить +layerClassметод вашего представления, чтобы вернуть класс вашего нового подкласса CALayer.

Оле Бегеманн
источник
Override + layerClass, как в случае переопределения слоя OpenGL? Думаю так. Установить рамки подслоев? Установить на что? Я должен рассчитывать кадры / позиции всех подслоев индивидуально в зависимости от размера кадра суперслоев? Нет ли решения, подобного ограничениям или связям с суперуровнем? О боже, еще один день вне временных рамок. Размер CGLayers был изменен, но был слишком медленным, CALayers достаточно быстр, но не изменился. Так много сюрпризов. В любом случае спасибо за ответ.
Geri Borbás,
3
В CALayer на Mac для этого есть менеджеры компоновки, но они недоступны на iPhone. Так что да, вам придется рассчитывать размеры кадра самостоятельно. Другой вариант - использовать подвиды вместо подслоев. Затем вы можете соответственно установить маски автоизменения размеров подвидов.
Оле Бегеманн,
2
Да, UIViews слишком дорогие по производительности классы, поэтому я использую CALayers.
Geri Borbás,
Я попробовал, как вы предложили. Это работает, а это нет. Я изменяю размер анимированного вида, но размеры подслоев меняются в другой анимации. Черт возьми, что теперь делать? Какой метод переопределить в подклассе CALayer, который вызывается на КАЖДОМ (!) Кадре анимации представления? Есть ли?
Geri Borbás,
Просто столкнулся с этим. Похоже, что лучший вариант - создать подкласс CALayer, как сказал Оле, но это кажется излишне громоздким.
Дмитрий
15

Я использовал это в UIView.

-(void)layoutSublayersOfLayer:(CALayer *)layer
{
    if (layer == self.layer)
    {
        _anySubLayer.frame = layer.bounds;
    }

super.layoutSublayersOfLayer(layer)
}

Работает для меня.

Руно Сахара
источник
Да, у меня это сработало в iOS 8. Предположительно, будет работать и в более ранних версиях. У меня был фоновый слой с градиентом, и мне нужно было изменить размер слоя при повороте устройства.
EPage_Ed
Работал для меня, но это нужно вызов через супер
энтропию
Это лучший ответ! Да, я пробовал layoutSubviews, но он только нарушает макет моего UITextField, например, исчезают leftView и rightView и исчезает больше текста в UITextField. Возможно, я использовал расширение вместо наследования UIView, но это было очень глючно. ЭТО РАБОТАЕТ ОТЛИЧНО! THX
Michał Ziobro
Для слоя с нестандартным рисунком ( CALayerDelegateами draw(_:in:)) мне также пришлось позвонить _anySubLayer.setNeedsDisplay(). Тогда это сработало для меня.
jedwidz
9

У меня такая же проблема. В слое настраиваемого вида я добавил еще два подслоя. Чтобы изменить размер подслоев (каждый раз, когда меняются границы настраиваемого представления), я реализовал метод layoutSubviewsмоего настраиваемого представления; внутри этого метода я просто обновляю фрейм каждого подслоя, чтобы он соответствовал текущим границам слоя моего подпредставления.

Что-то вроде этого:

-(void)layoutSubviews{
   //keep the same origin, just update the width and height
   if(sublayer1!=nil){
      sublayer1.frame = self.layer.bounds;
   }
}
Солин
источник
16
К вашему сведению, вам не нужно if (sublayer1! = Nil), поскольку ObjC определяет сообщения равными нулю (в данном случае -setFrame :) как безоперационный возврат 0.
Эндрю Пулиот
7

2017 г.

Буквальный ответ на этот вопрос:

«Размер CALayers не изменился при изменении границ UIView. Почему?»

это к лучшему или к худшему

needsDisplayOnBoundsChange

по умолчанию false в CALayer.

решение,

class CircularGradientViewLayer: CALayer {
    
    override init() {
        
        super.init()
        needsDisplayOnBoundsChange = true
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override open func draw(in ctx: CGContext) {
        go crazy drawing in .bounds
    }
}

Действительно, направляю вас в этот QA

https://stackoverflow.com/a/47760444/294884

что объясняет, что, черт возьми, contentsScaleделает критическая настройка; вам обычно необходимо установить это одинаково при установке needsDisplayOnBoundsChange.

Толстяк
источник
5

Версия Swift 3

В настраиваемой ячейке добавьте следующие строки

Объявить первым

let gradientLayer: CAGradientLayer = CAGradientLayer()

Затем добавьте следующие строки

override func layoutSubviews() {
    gradientLayer.frame = self.YourCustomView.bounds
}
Винай Кришна Гупта
источник
0

Как писал [Оле], CALayer не поддерживает автоматическое изменение размера на iOS. Поэтому вам следует настроить макет вручную. Моим вариантом было настроить рамку слоя внутри (iOS 7 и ранее)

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration 

или (начиная с iOS 8)

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinato
DevGansta
источник
0

В вашем пользовательском представлении вам нужно объявить переменную для вашего пользовательского уровня, не объявляйте переменную в области init. И просто запустите его один раз, не пытайтесь установить нулевое значение и повторно запустить

    class CustomView: UIView {
        var customLayer: CALayer = CALayer ()

        переопределить func layoutSubviews () {
            super.layoutSubviews ()
    // охранник let _fillColor = self._fillColor else {return}
            initializeLayout ()
        }
        private func initializeLayout () { 
            customLayer.removeFromSuperView ()
            customLayer.frame = layer.bounds
                   
            layer.insertSubview (at: 0)
        }
    }
Ле Тон Тхань
источник