How to autoresize sublayers - iphone

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()
}

Related

Determine width of UIView in Swift

I have an autolayouted UIView and need to know the width of it. How do I find the width the easiest way in Swift?
You can find the width of any view by
let width = yourView.bounds.width
If you have applied a width constraint to the view and you have an outlet for that constraint then you can find it by.
let width = yourWidthConstraint.constant
The right place to get the UIScreen frame data is in viewDidLayoutSubviews as it is called after the subViews have been laid out in screen also it is called after every time your device changes orientation such as your width will be different when your user goes into landscape mode.This is called after viewWillAppear:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let viewWidth = self.myView.bounds.width
}
Inside implementation your view you can use
override func layoutSubviews() {
super.layoutSubviews()
// you can get width of view in this function
print(self.frame.size.width)
}

How to simply animate an UIimageView?

I want to move a 40, 40 pixel square from the top to the bottom, then repeat that action over a period of 3 seconds. Where would I put the entire thing in the viewcontroller? There is no start button so would I put in in the viewdidload?
So far I have this
UIView.animateWithDuration(0.2, animations: {
}, completion: {
})
As somewhere to start. Would I set the image to equal UIView? What should It look like? the image is called mrock.
I tried using blank.moveToPoint(CGPoint(x: 0,y: 0)) in the animations part, but dont I need A UIBezierPath thing?
As #picciano stated you should have an IBOutlet to an UIImageView if you are using storyboards. So you in your view controller you can try something like this:
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//'img' is a picture imported in your project
self.imageView.image = UIImage(named: "img")
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let newPos: CGFloat = 300.0
UIView.animateWithDuration(1.0, animations: { () -> Void in
self.imageView.frame = CGRectMake(0, newPos, CGFloat(self.imageView.bounds.size.width), CGFloat(self.imageView.bounds.size.height))
})
}
First, consider putting the animation in viewWillAppear, then disable it if needed in viewDidDisappear.
Second, if you're using auto layout, you will probably need to create an IBOutlet for the relevant constraint and animate the constant property of the constraint rather than the position of the view object directly.

check if UIView is in UIScrollView visible state

What is the easiest and most elegant way to check if a UIView is visible on the current UIScrollView's contentView? There are two ways to do this, one is involving the contentOffset.y position of the UIScrollView and the other way is to convert the rect area?
If you're trying to work out if a view has been scrolled on screen, try this:
CGRect thePosition = myView.frame;
CGRect container = CGRectMake(scrollView.contentOffset.x, scrollView.contentOffset.y, scrollView.frame.size.width, scrollView.frame.size.height);
if(CGRectIntersectsRect(thePosition, container))
{
// This view has been scrolled on screen
}
Swift 5: in case that you want to trigger an event that checks that the entire UIView is visible in the scroll view:
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.bounds.contains(targetView.frame) {
// entire UIView is visible in scroll view
}
}
}
Implement scrollViewDidScroll: in your scroll view delegate and calculate manually which views are visible (e.g. by checking if CGRectIntersectsRect(scrollView.bounds, subview.frame) returns true.
updated for swift 3
var rect1: CGRect!
// initialize rect1 to the relevant subview
if rect1.frame.intersects(CGRect(origin: scrollView.contentOffset, size: scrollView.frame.size)) {
// the view is visible
}
I think your ideas are correct. if it was me i would do it as following:
//scrollView is the main scroll view
//mainview is scrollview.superview
//view is the view inside the scroll view
CGRect viewRect = view.frame;
CGRect mainRect = mainView.frame;
if(CGRectIntersectsRect(mainRect, viewRect))
{
//view is visible
}
José's solution didn't quite work for me, it was detecting my view before it came on screen. The following intersects code works perfect in my tableview if José's simpler solution doesn't work for you.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let viewFrame = scrollView.convert(targetView.bounds, from: targetView)
if viewFrame.intersects(scrollView.bounds) {
// targetView is visible
}
else {
// targetView is not visible
}
}
Solution that takes into account insets
public extension UIScrollView {
/// Returns `adjustedContentInset` on iOS >= 11 and `contentInset` on iOS < 11.
var fullContentInsets: UIEdgeInsets {
if #available(iOS 11.0, *) {
return adjustedContentInset
} else {
return contentInset
}
}
/// Visible content frame. Equal to bounds without insets.
var visibleContentFrame: CGRect {
bounds.inset(by: fullContentInsets)
}
}
if scrollView.visibleContentFrame.contains(view) {
// View is fully visible even if there are overlaying views
}

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)
}
}

iPhone: Resize CALayer sublayers in view [duplicate]

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)
}
}