CALayer with different effects between iPhone / iPad - ios

This has me a bit confused. I have a universal app which has two different Storyboards for iPhone and iPad. I'm using a small block of CALayer code to round the edges of a view and throw a shadow on it. Works perfectly on the iPhone.
[self configShadowLayer:_view
cornerRadius:_view.frame.size.width / 2.0
shadowOffsetX:0.0
shadowOffsetY:3.0
shadowRadius:2.0
opacity:0.2];
-(void)configShadowLayer:(UIView *)shadowView
cornerRadius:(float)cornerRadius
shadowOffsetX:(float)shadowOffsetX
shadowOffsetY:(float)shadowOffsetY
shadowRadius:(float)shadowRadius
opacity:(float)opacity {
CALayer *shadowLayer = shadowView.layer;
shadowLayer.masksToBounds = NO;
shadowLayer.cornerRadius = cornerRadius;
shadowLayer.shadowOffset = CGSizeMake(shadowOffsetX, shadowOffsetY);
shadowLayer.shadowRadius = shadowRadius;
shadowLayer.shadowOpacity = opacity;
}
On the iPad, it gives me a diamond shape. I have to change the corner radius from the half to a quarter and then it works. But then of course the iPhone view goes from a circle to a rounded rect.
What am I missing?

You should call configShadowLayer:cornerRadius:shadowOffsetX:shadowOffsetY:shadowRadius:opacity: after the layout of the view is done. When you call it only before the layout process the view's frame has probably the wrong sizes, and you will get a corner radius which is too large.
BTW: You should compute the radius depending to the bounds of the view, because the bounds represents the coordinate system inside of the view.

Related

What's currently the "correct" way to set a UIView's corner radius?

Setting a UIView's corner radius can be done the following ways:
Set the layer's cornerRadius property:
view.layer.cornerRadius = 5;
view.layer.masksToBounds = true;
Apply a mask:
func roundCorners(corners:UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
Override draw(_:):
func draw(_ rect: CGRect) {
// Size of rounded rectangle
let rectWidth = rect.width
let rectHeight = rect.height
// Find center of actual frame to set rectangle in middle
let xf: CGFloat = (self.frame.width - rectWidth) / 2
let yf: CGFloat = (self.frame.height - rectHeight) / 2
let ctx = UIGraphicsGetCurrentContext()!
ctx.saveGState()
let rect = CGRect(x: xf, y: yf, width: rectWidth, height: rectHeight)
let clipPath = UIBezierPath(roundedRect: rect, cornerRadius: rectCornerRadius).cgPath
ctx.addPath(clipPath)
ctx.setFillColor(rectBgColor.cgColor)
ctx.closePath()
ctx.fillPath()
ctx.restoreGState()
}
Which of these is generally considered to be the "correct" way of implementing rounded corners on a UIView, accounting for the following criteria:
configuration (some corners may be rounded while others are not)
animation (can you animate the cornerRadius changing)
flexibility (does it break third party libraries or masks you have already applied)
readability (how concise/reusable is the solution)
speed (does it negatively impact performance)
Note that I don't know what's currently the “correct” way to set a UIView's corner radius.
What I prefer to do is to use Interface Builder as much as possible without having extra code which this approach shows and is reliable to my experience.
From iOS 11 upwards
you can use user-defined runtime attributes in the Identity inspector of the Interface Builder by setting the following properties:
layer.cornerRadius
layer.maskedCorners
layer.masksToBounds
According to the documentation of the CACornerMask you can see that the maskedCorners property is in fact a NSUInteger data type and you're allowed to set the following values:
kCALayerMinXMinYCorner = 1U << 0
kCALayerMaxXMinYCorner = 1U << 1
kCALayerMinXMaxYCorner = 1U << 2
kCALayerMaxXMaxYCorner = 1U << 3
Since you're allowed to bitwise OR those masks together you only have to "calculate" the resulting integer of that bitwise OR of what you actually need.
Therefore set the following number (integer) values for the maskedCorners property to get rounded corners:
0 = no corner is being rounded
1 = top left corner rounded only
2 = top right corner rounded only
3 = top left and top right corners rounded only
4 = bottom left corner rounded only
5 = top left and bottom left corners rounded only
6 = top right and bottom left corners rounded only
7 = top left, top right and bottom left corners rounded only
8 = bottom right corner rounded only
9 = top left and bottom right corners rounded only
10 = top right and bottom right corners rounded only
11 = top left, top right and bottom right corners rounded only
12 = bottom left and bottom right corners rounded only
13 = top left, bottom left and bottom right corners rounded only
14 = top right, bottom left and bottom right corners rounded only
15 = all corners rounded
Example: If you want to set the corner radius for the top-left and the top-right corners of a UIView you would use those attributes:
Re your three options:
Using CALayer existing properties: This is an easy (and likely the most efficient) solution for simple corner masking. It is animatable, too. In iOS 11 and later, you can pick which corners are to be masked.
Re custom CAShapeLayer masks: This is nice approach if the corner masking is not simple corner rounding but some arbitrary path. You have to be cautious to make sure to update this mask if the frame changes (e.g. update the path in layoutSubviews of view or in viewDidLayoutSubviews of controller).
Admittedly, if you want to do a very graceful animation as the view’s frame changes, that takes a little more work. But, as I point out above, simply responding to frame changes in layoutSubviews or viewDidLayoutSubviews is quite simple and takes care of it if you are not too worried about the corner rounding mid-animation.
Re custom draw(_:): This is more work than it is worth and you are probably not enjoying optimizations that Apple’s team may have done behind the scenes (e.g. what if subsequent draw calls are only drawing a portion of the full bounds; your code is redrawing the whole thing regardless).
I would suggest option 1 for simple cases, and option 2 if you need more control than option 1 can offer. But there is no “best” approach: It depends upon what you need and how much work you are willing to go through.
I did a couple of test with iOS 11 or lower version and the best practice I discovered for me to round a specific or all corners, you can do with the next code.
// Full size
CGSize vSize = [UIScreen mainScreen].bounds.size;
// Object
UIView *viewTest = [[UIView alloc] initWithFrame: CGRectMake(0, 0, vSize.width, vSize.height)];
[viewTest setAutoresizingMask: UIViewAutoresizingFlexibleHeight];
[viewTest setBackgroundColor: [UIColor grayColor]];
// maskedCorners is only available in iOS 11
if (#available(iOS 11.0, *)) {
[viewTest setClipsToBounds: YES];
[viewTest.layer setCornerRadius: 10];
// Only if you want to round the left and right top corners
[viewTest.layer setMaskedCorners: kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner];
}
else {
// The old way used in lower version
CAShapeLayer *shapeLayerObj = [CAShapeLayer layer];
[shapeLayerObj setPath: [UIBezierPath bezierPathWithRoundedRect: viewTest.bounds byRoundingCorners: UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii: (CGSize){10.0, 10.}].CGPath];
[viewTest.layer setMask: shapeLayerObj];
}
This code fix the problem with autoResizingMask doesn't work when it use the old way to round corners.
Fix a bug with UIScrollView with round corners and setContentSize major to height of object.
The swift version it's something like this
let view = UIView()
view.clipsToBounds = true
view.layer.cornerRadius = 8
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
I think this is the most comprehensive summary of all: http://texturegroup.org/docs/corner-rounding.html
My heuristic is that if the view doesn't need a high performance (e.g. it's not inside a table view cell), the easiest option is using CALayer's cornerRadius. If you need some more advanced corner radius or high performance, then it's better to explore other options.
I go with the first one, it is the cleaner way of doing and you can do it in the IDE without code. Open the attributes inspector and then click on the Identity inspector and add under "User Defined Runtime attributes" those 2 properties:
contView.layer.cornerRadius = 25
contView.layer.maskedCorners = [.layerMaxXMinYCorner,.layerMinXMinYCorner]
contView.layer.masksToBounds = true
this is the result just top left and top right corners

Rounding edge on UITextField

I have 3 UITextFields with border style none. I want to add borders in code. The effect I want to achieve is to have rounded top corners on first UITextField and to have rounded bottom corners on third text field. Code I am using for rounding edges is here Round top corners of a UIView and add border
But i get this - no right edge and corners are not rounded:
Note: I've set all constraints, that is not a problem. If i use UITextBorderStyleLine right edge is not rounded again.
Please help.
if you want to simplest way to do like on a screen look here>>>
Grey view with clip subviews mode on, and 3 labels/textfields inside, and 2 black view with 1 pixel height
in code..
self.viewCorner.layer.cornerRadius = 6;
self.viewCorner.layer.borderWidth = 1;
self.viewCorner.layer.borderColor = [UIColor blackColor].CGColor;
After you set constraints to grey view and 2 views with 1 pixel height like this
Grey view
1 pixel height view
and result on IPad simulator
Thats all, you can do this for 5 minutes
You need to create custom UItextField or method to change the top and bottom corner to oval shape. Here is a below sample code to top corner similarly you need to do it for bottom left and right corner.
CGRect rect = myTextField.bounds;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect
byRoundingCorners:UIRectCornerTopLeft |UIRectCornerTopRight
cornerRadii:CGSizeMake(6.0, 6.0)];
CAShapeLayer *layers = [CAShapeLayer layer];
layers.frame = rect;
layers.path = path.CGPath;
myTextField.layer.mask = layers;

Why is my rectangle being drawn wrong?

I'm trying to draw a rectangle around my screen. Here's my code:
let viewRect = SKShapeNode(rect: self.view!.frame)
viewRect.strokeColor = SKColor.whiteColor()
viewRect.lineWidth = 2.0
addChild(viewRect)
I'm using self.view!.frame instead of self.frame because my scene is not the same size as my screen (the scene is stretched to fill my screen).
I would have expected the code to draw a rectangle around my screen, but it draws this instead (the white rectangle is what is drawn):
Anyone know why it's not being drawn around my screen?
Scene size: 1024x768
View size: 414x736
Scale mode: Aspect Fill
You are creating your border as a node within the scene but then setting its coordinates according to the view that is presenting the scene. The SKScene's coordinate system is not generally the same as the view presenting it so that is why the shape is different to what you expect.

GLKViewController rotating size

I have set up a simple layout with a single custom GLKViewController. Inside the GLKViewController is a normal GLKView. When the phone orientation changes the view is smoothly autorotated and stretched to match the new size of the view.
Is it possible to acquire the width and the height of the GLKView while the rotation is still in progress, from the glkView:drawInRect: method in my custom GLKViewController?
I'm interested in the width and height marked in blue:
The rotation width and height can be acquired from the presentationLayer of the GLKView:
CALayer *presentationLayer = [self.view.layer presentationLayer];
CGSize layerSize = presentationLayer.bounds.size;
Answered here: https://stackoverflow.com/a/18742030/1109997

UIView UIScrollview - frames, bounds, center confusions

I am putting a UIImageView inside a UIScrollView, and trying to control the image so that it is centred on the scrollview after a zoom. and I am not sure the best way to do this.
The apple docs tell us NOT to use the frame property: "Warning If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored." So I am attempting using the following in a UIViewController subclass whose xib contains a scrollView and contained imageView:
scrollView.bounds =
CGRectMake
(scrollView.contentSize.width/2 - scrollView.center.x,
scrollView.contentSize.height/2 - scrollView.center.y,
scrollView.bounds.size.width,
scrollView.bounds.size.height);
containedView.center =
CGPointMake
(containedView.bounds.size.width*scrollView.zoomScale/2,
containedView.bounds.size.height*scrollView.zoomScale/2);
This works accurately where the width and height of the containedView is larger than that of the scrollView and sets the views so that subsequent scrolling will take you exactly to the edges of the containedView. However when either dimension of the image is smaller than the scrollView width and height the image is magnetically attracted to the top left corner of the screen. In the iPad Simulator (only) when the images is shrunk to the size of minimumZoom it does lock on to the centre of the screen. The magnetic attraction is very smooth as if something in the UI is overriding my code after the image has been centred. It looks a bit like a CALayer contentsGravity ( kCAGravityTopLeft ) thing, maybe?
Apple contradict their own advice in their code sample, photoScroller (in a subclass of UIScrollView):
// center the image as it becomes smaller than the size of the screen
CGSize boundsSize = self.bounds.size;
CGRect frameToCenter = imageView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width)
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
else
frameToCenter.origin.x = 0;
// center vertically
if (frameToCenter.size.height < boundsSize.height)
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
else
frameToCenter.origin.y = 0;
imageView.frame = frameToCenter;
This method does a better job of centring when the image is smaller, but when I try this on my project it introduces some kind of inconsistencies. For example, with scrollView.bounces = NO, a horizontal image whose height is smaller than the height of the scrollView but whose width is larger (so it can be scrolled from left to right) will scroll further to the left than it should (when scrolling to the right it stops correctly at the edge of the image, although if scrollView.bounces = YES it then bounces in from the edge so the image is always cropped on the left) When the image is larger in both dimensions than its containing scrollview this issue accentuates and the whole result feels broken, which is unsurprising given Apple's documented advice.
I have scoured the forums and can't find much comment on this. Am I missing something really obvious?
You don't appear to be using the transform property, so you can ignore that warning about not using the frame property when using the transform property. Go ahead and use the frame property, just like Apple (and the rest of us) do.

Resources