iPhone: Resize CALayer sublayers in view [duplicate] - iphone

I have a UIView which has about 8 different CALayer sublayers added to its layer.
If I modify the view's bounds (animated), then the view itself shrinks (I checked it with a backgroundColor), but the sublayers' size remains unchanged.
How to solve this?

I used the same approach that Solin used, but there's a typo in that code. The method should be:
- (void)layoutSubviews {
[super layoutSubviews];
// resize your layers based on the view's new bounds
mylayer.frame = self.bounds;
}
For my purposes, I always wanted the sublayer to be the full size of the parent view. Put that method in your view class.

Since CALayer on the iPhone does not support layout managers, I think you have to make your view's main layer a custom CALayer subclass in which you override layoutSublayers to set the frames of all sublayers. You must also override your view's +layerClass method to return the class of your new CALayer subclass.

I used this in the UIView.
-(void)layoutSublayersOfLayer:(CALayer *)layer
{
if (layer == self.layer)
{
_anySubLayer.frame = layer.bounds;
}
super.layoutSublayersOfLayer(layer)
}
Works for me.

I had the same problem. In a custom view's layer I added two more sublayers. In order to resize the sublayers (every time the custom view's boundaries change), I implemented the method layoutSubviews of my custom view; inside this method I just update each sublayer's frame to match the current boundaries of my subview's layer.
Something like this:
-(void)layoutSubviews{
//keep the same origin, just update the width and height
if(sublayer1!=nil){
sublayer1.frame = self.layer.bounds;
}
}

2017:
The literal answer to this question:
"CALayers didn't get resized on its UIView's bounds change. Why?"
is that for better or worse:
needsDisplayOnBoundsChange
defaults to false (!!) in CALayer.
solution,
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
}
}
Indeed, I direct you to this QA
https://stackoverflow.com/a/47760444/294884
which explains, what the hell the critical contentsScale does. You usually need to set that, when you set needsDisplayOnBoundsChange.

Swift 3 Version
In Custom cell, Add Following lines
Declare first
let gradientLayer: CAGradientLayer = CAGradientLayer()
Then add following lines
override func layoutSubviews() {
gradientLayer.frame = self.YourCustomView.bounds
}

As [Ole] wrote CALayer does not support autoresizing on iOS. So you should adjust layout manually. My option was to adjust layer's frame within (iOS 7 and earlier)
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
or (as of iOS 8)
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinato

In your custom view, you need declare variable for your custom layer, don't declare variable in scope init. And just init it once time, don't try set null value and reinit
class CustomView:UIView {
var customLayer:CALayer = CALayer()
override func layoutSubviews() {
super.layoutSubviews()
// guard let _fillColor = self._fillColor else {return}
initializeLayout()
}
private func initializeLayout() {
customLayer.removeFromSuperView()
customLayer.frame = layer.bounds
layer.insertSubview(at:0)
}
}

Related

Any way to opt out of autoresizing permanently?

I'm writing nib-less views in which I use autolayout for all my layout logic. I find myself having to turn off autoresizing with every view I instantiate. My code is littered with a lot of these:
view.translatesAutoresizingMaskIntoConstraints
Ideally I'd like to just
extension UIView/NSView {
override var translatesAutoresizingMaskIntoConstraints: Bool = false
}
and get it over with once and for all, but extensions can't override stored properties.
Is there some other simple way to switch off autoresizing for good?
Well just a suggestion since its annoying to always set that to false, just setup a function with all the shared setups for the UIView and call it every time,
its saves time and its kinda less annoying than trying and setting the values each time,
extension UIView {
func notTranslated() {
self.translatesAutoresizingMaskIntoConstraints = false
//Add any additional code.
}
}
//Usage
let view = UIView()
view.notTranslated()
You can't override this constraints properties because the UIView maybe declared in the IB
translatesAutoresizingMaskIntoConstraints according to apple.
By default, the property is set to true for any view you programmatically create. If you add views in Interface Builder, the system automatically sets this property to false.
imagine if you could override that from an extension that would lead to some conflicts if there was other UIView's that's have the opposite value True || false, so in my opinion:
Apple did this to prevent any conflicts with the views constrains, therefore if you don't like to write it every time just wrap it up in a function.
Please if anyone have additional information, don't hesitate to contribute.
UPDATE: I found this cool answer that could also work, check out the code below.
class MyNibless: UIView {
//-----------------------------------------------------------------------------------------------------
//Constructors, Initializers, and UIView lifecycle
//-----------------------------------------------------------------------------------------------------
override init(frame: CGRect) {
super.init(frame: frame)
didLoad()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
didLoad()
}
convenience init() {
self.init(frame: CGRect.zero)
}
func didLoad() {
//Place your initialization code here
//I actually create & place constraints in here, instead of in
//updateConstraints
}
override func layoutSubviews() {
super.layoutSubviews()
//Custom manually positioning layout goes here (auto-layout pass has already run first pass)
}
override func updateConstraints() {
super.updateConstraints()
//Disable this if you are adding constraints manually
//or you're going to have a 'bad time'
//self.translatesAutoresizingMaskIntoConstraints = false
translatesAutoresizingMaskIntoConstraints = false
//Add custom constraint code here
}
}
var nibless: UIView = MyNibless()
//Usage
nibless.updateConstraints()
print(nibless.translatesAutoresizingMaskIntoConstraints) //false
So simply just create MyNibless instance as UIView and this also open big door to customizations too

UIButton Subclass using its size to set corner radius with Autolayout

I have the following subclass of UIButton just to give a circular border to a UIButton, assuming height and width of UIButton are set equal. But when auto layout does its job I do not get the correct radius. How can I change this class to behave correctly with auto layout?
import UIKit
class CircularButton: UIButton {
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
self.layer.cornerRadius = self.bounds.size.height / 2
self.layer.borderWidth = 1
}
}
Always call:
setNeedsLayout()
layoutIfNeeded()
before accessing a view's frame or bounds when using AutoLayout. Otherwise, your accessing the view's size before its been set by AutoLayout. Calling these two methods forces an AutoLayout pass, allowing you to access the view's correct size.
Also, you should set the corner radius in awakeFromNib instead of init.

Swift: Reusable UIView in storyboard and sizing constraints

I'm trying to create a reusable UIView in Swift that I can plug into my Storyboard view controllers. My key issue right now is that the reusable UIView "widget" doesn't fully fit into the UIView box in the storyboard. I followed this tutorial to set up the reusable UIView widget
Created a subclass of UIView and a corresponding .xib -- and connected these:
import UIKit
class MyWidgetView: UIView {
#IBOutlet var view: UIView!;
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
NSBundle.mainBundle().loadNibNamed("MyWidgetView", owner: self, options: nil);
self.addSubview(self.view);
}
}
In the XIB, which is the interface file corresponding to the code above, I used UIView with Freeform size under the Simulated Metrics, and Scale to Fill under View mode.
In the main storyboard, I added a UIView block (same rectangular shape) and changed the Class to MyWidgetView
It works, but the components I created in the XIB look squished in the actual app, despite the fact that I used layout constraints in both the XIB and also the main storyboard.
See the screenshot. The pink part isn't supposed to appear, since that is just a color of the UIVIew on the main storyboard that I added to test the sizing. That UIView is actually MyWidgetView (after I changed the class in step 3. So in theory, since MyWidgetView == the UIView on the main storyboard, and that UIView has constraints that make it rectangular in the superview, then why is my widget squished? The blue part below should extend all the way right.
The actual view hierarchy loaded from the nib file in your code is added via
self.addSubview(self.view). So, the frame of your self.view actually has no relationship with its parent, i.e. MyWidgetView.
You may choose either adding layout constraints through code or just setting its frame after being added as a subview. Personally, I prefer the latter. In my experiment, the following is what works for me. I am using Xcode 6.4, which I think is not the same one as yours.
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if let nibsView = NSBundle.mainBundle().loadNibNamed("MyWidgetView", owner: self, options: nil) as? [UIView] {
let nibRoot = nibsView[0]
self.addSubview(nibRoot)
nibRoot.frame = self.bounds
}
}
Alternatively the variable frame can be overridden. This code worked for me when CardImgText was set to files owner for the view.
class CardImgTxt: NSView {
#IBOutlet var view: NSView!
override var frame: NSRect{
didSet{
view.frame = bounds
}
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
// Drawing code here.
}
required init?(coder: NSCoder) {
super.init(coder: coder)
NSBundle.mainBundle().loadNibNamed("View", owner: self, topLevelObjects: nil)
addSubview(view)
}
}
if you are more interested in efficiency than real time updating. Then replace :
override var frame: NSRect{
didSet{
view.frame = bounds
}
}
with:
override func viewDidEndLiveResize() {
view.frame = bounds
}

How to autoresize sublayers

I have an imageview and on button click I draw some bezier paths and some text layer too. On undo, I've removed last drawn path and text layer.
Now I want to resize my image View to set up in uper half portion of view and duplicated in lower half view, and want to resize all layers at the same time and also want to continue drawing after that.
Its too late to answer...
But may be this can help others. There is no autoresize masks for CALayers
You can manage that with some tricks..
- (void)layoutSubviews {
// resize your layers based on the view's new frame
layer.frame = self.bounds;
}
This will work if you have created a subclass of UIView. If not that, you can just set the bounds to the frame of CALayer anywhere if you have reference to your layer.
At least there are 3 options:
use delegate of CALayer
override layoutSublayers, and call it by setNeedsLayout()
use extension and call view's fitLayers
Example below, option 3:
Swift 5, 4
Code
extension UIView {
func fitLayers() {
layer.fit(rect: bounds)
}
}
extension CALayer {
func fit(rect: CGRect) {
frame = rect
sublayers?.forEach { $0.fit(rect: rect) }
}
}
Example
// inside UIViewController
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
view.fitLayers()
}
// or
// inside UIView
override func layoutSubviews() {
super.layoutSubviews()
fitLayers()
}

CALayers didn't get resized on its UIView's bounds change. Why?

I have a UIView which has about 8 different CALayer sublayers added to its layer.
If I modify the view's bounds (animated), then the view itself shrinks (I checked it with a backgroundColor), but the sublayers' size remains unchanged.
How to solve this?
I used the same approach that Solin used, but there's a typo in that code. The method should be:
- (void)layoutSubviews {
[super layoutSubviews];
// resize your layers based on the view's new bounds
mylayer.frame = self.bounds;
}
For my purposes, I always wanted the sublayer to be the full size of the parent view. Put that method in your view class.
Since CALayer on the iPhone does not support layout managers, I think you have to make your view's main layer a custom CALayer subclass in which you override layoutSublayers to set the frames of all sublayers. You must also override your view's +layerClass method to return the class of your new CALayer subclass.
I used this in the UIView.
-(void)layoutSublayersOfLayer:(CALayer *)layer
{
if (layer == self.layer)
{
_anySubLayer.frame = layer.bounds;
}
super.layoutSublayersOfLayer(layer)
}
Works for me.
I had the same problem. In a custom view's layer I added two more sublayers. In order to resize the sublayers (every time the custom view's boundaries change), I implemented the method layoutSubviews of my custom view; inside this method I just update each sublayer's frame to match the current boundaries of my subview's layer.
Something like this:
-(void)layoutSubviews{
//keep the same origin, just update the width and height
if(sublayer1!=nil){
sublayer1.frame = self.layer.bounds;
}
}
2017:
The literal answer to this question:
"CALayers didn't get resized on its UIView's bounds change. Why?"
is that for better or worse:
needsDisplayOnBoundsChange
defaults to false (!!) in CALayer.
solution,
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
}
}
Indeed, I direct you to this QA
https://stackoverflow.com/a/47760444/294884
which explains, what the hell the critical contentsScale does. You usually need to set that, when you set needsDisplayOnBoundsChange.
Swift 3 Version
In Custom cell, Add Following lines
Declare first
let gradientLayer: CAGradientLayer = CAGradientLayer()
Then add following lines
override func layoutSubviews() {
gradientLayer.frame = self.YourCustomView.bounds
}
As [Ole] wrote CALayer does not support autoresizing on iOS. So you should adjust layout manually. My option was to adjust layer's frame within (iOS 7 and earlier)
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
or (as of iOS 8)
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinato
In your custom view, you need declare variable for your custom layer, don't declare variable in scope init. And just init it once time, don't try set null value and reinit
class CustomView:UIView {
var customLayer:CALayer = CALayer()
override func layoutSubviews() {
super.layoutSubviews()
// guard let _fillColor = self._fillColor else {return}
initializeLayout()
}
private func initializeLayout() {
customLayer.removeFromSuperView()
customLayer.frame = layer.bounds
layer.insertSubview(at:0)
}
}