Reputation: 7536
Hi I am developing iPhone application in which I tried to set one side border for edittext. I did this in following way:
int borderWidth = 1;
CALayer *leftBorder = [CALayer layer];
leftBorder.borderColor = [UIColor whiteColor].CGColor;
leftBorder.borderWidth = borderWidth;
leftBorder.frame = CGRectMake(0, textField.frame.size.height - borderWidth, textField
.frame.size.width, borderWidth);
[textField.layer addSublayer:leftBorder];
I put some constraints on my edittext in IB so that when I rotate my device it will adjust width of text field according to that. My problem is that adjusts the width of edittext not adjusting the width of CALayer which I set for my edit text. So I think I have to put some constraints for my CALayer item as well. But I dont know how to do that. ANy one knows about this? Need Help. Thank you.
Upvotes: 83
Views: 55421
Reputation: 1371
Riffing off arango_86's answer – if you are applying the KVO fix within your own UIView subclass, the more "Swifty" way to do this is to override bounds
and use didSet
on it, like so:
override var bounds: CGRect {
didSet {
layer.frame = bounds
}
}
Upvotes: 1
Reputation: 4425
In Swift 5
, you can try the following solution:
Use KVO, for the path "YourView.bounds" as given below.
self.addObserver(self, forKeyPath: "YourView.bounds", options: .new, context: nil)
Then handle it as given below.
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "YourView.bounds" {
YourLayer.frame = YourView.bounds
return
}
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
More info about this here
Upvotes: 14
Reputation: 1508
When I have to apply KVO I prefer to do it with RxSwift (Only if I am using it for more stuff, do not add this library just for this.)
You can apply KVO with this library too, or in the viewDidLayoutSubviews
but I've had better results with this.
view.rx.observe(CGRect.self, #keyPath(UIView.bounds))
.subscribe(onNext: { [weak self] in
guard let bounds = $0 else { return }
self?.YourLayer.frame = bounds
})
.disposed(by: disposeBag)
Upvotes: 1
Reputation: 4042
Swift 3.x KVO Solution (Updated @arango_86's answer)
Add Observer
self.addObserver(
self,
forKeyPath: "<YOUR_VIEW>.bounds",
options: .new,
context: nil
)
Observe Values
override open func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?
) {
if (keyPath == "<YOUR_VIEW.bounds") {
updateDashedShapeLayerFrame()
return
}
super.observeValue(
forKeyPath: keyPath,
of: object,
change: change,
context: context
)
}
Upvotes: 1
Reputation: 778
My solution when I create with dashed border
class DashedBorderView: UIView {
let dashedBorder = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
//custom initialization
dashedBorder.strokeColor = UIColor.red.cgColor
dashedBorder.lineDashPattern = [2, 2]
dashedBorder.frame = self.bounds
dashedBorder.fillColor = nil
dashedBorder.cornerRadius = 2
dashedBorder.path = UIBezierPath(rect: self.bounds).cgPath
self.layer.addSublayer(dashedBorder)
}
override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
dashedBorder.path = UIBezierPath(rect: self.bounds).cgPath
dashedBorder.frame = self.bounds
}
}
Upvotes: 7
Reputation: 3759
According to this answer, layoutSubviews
does not get called in all needed cases. I have found this delegate method more effective:
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self != nil) {
[self.layer addSublayer:self.mySublayer];
}
return self;
}
- (void)layoutSublayersOfLayer:(CALayer*)layer
{
self.mySublayer.frame = self.bounds;
}
Upvotes: 9
Reputation: 2193
I had a similar problem - needing to set the frame of a 'CALayer' when using auto layout with views (in code, not IB).
In my case, I had a slightly convoluted hierarchy, having a view controller within a view controller. I ended up at this SO question and looked into the approach of using viewDidLayoutSubviews
. That didn't work. Just in case your situation is similar to my own, here's what I found...
Overview
I wanted to set the frame for the CAGradientLayer
of a UIView
that I was positioning as a subview within a UIViewController
using auto layout constraints.
Call the subview gradientView
and the view controller child_viewController
.
child_viewController
was a view controller I'd set up as a kind of re-usable component. So, the view
of child_viewController
was being composed into a parent view controller - call that parent_viewController
.
When viewDidLayoutSubviews
of child_viewController
was called, the frame
of gradientView
was not yet set.
(At this point, I'd recommend sprinkling some NSLog
statements around to get a feel for the sequence of creation of views in your hierarchy, etc.)
So I moved on to try using viewDidAppear
. However, due to the nested nature of child_viewController
I found viewDidAppear
was not being called.
(See this SO question: viewWillAppear, viewDidAppear not being called, not firing).
My current solution
I've added viewDidAppear
in parent_viewController
and from there I'm calling viewDidAppear
in child_viewController
.
For the initial load I need viewDidAppear
as it's not until this is called in child_viewController
that all of the subviews have their frames set. I can then set the frame for the CAGradientLayer
...
I've said that this is my current solution because I'm not particularly happy with it.
After initially setting the frame of the CAGradientLayer
- that frame can become invalid if the layout changes - e.g. rotating the device.
To handle this I'm using viewDidLayoutSubviews
in child_viewController
- to keep the frame of the CAGradientLayer
within gradientView
, correct.
It works but doesn't feel good. (Is there a better way?)
Upvotes: -1
Reputation: 50089
the whole autoresizing business is view-specific. layers don't autoresize.
what you have to do -- in code -- is to resize the layer yourself
e.g.
in a viewController you would do
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews]; //if you want superclass's behaviour...
// resize your layers based on the view's new frame
self.editViewBorderLayer.frame = self.editView.bounds;
}
or in a custom UIView you could use
- (void)layoutSubviews {
[super layoutSubviews]; //if you want superclass's behaviour... (and lay outing of children)
// resize your layers based on the view's new frame
layer.frame = self.bounds;
}
Upvotes: 131