Shadow lagging the user interface - ios

I have around 15 views for which I apply QuartzCore shadow like this:
for button in buttonsArray {
button.layer.shadowOpacity = 0.75
button.layer.shadowColor = UIColor.blackColor().CGColor
button.layer.shadowRadius = 2.0
button.layer.shadowOffset = CGSize(width: 0.4, height: 1.2)
}
When I have a lot of this shadow in 1 view(like in this example) it really starts to slow and lag the user interface.
How can I fix it, or what other alternatives do I have? Thanks!

Check out the WWDC 2014 video 'Advanced Graphics and Animations for iOS Apps'.
https://developer.apple.com/videos/wwdc/2014/
35:50 in ... it talks about why using that type of code to generate a shadow causes extra off screen passes for the GPU as it figures out the shape of the shadow.
The suggested solution is to also use the shadowPath property on the layer if you already know the shape of the shadow...
It's a great video to watch.

Depending on how your buttons look, one idea would be to use a background image for the buttons that includes the shadow, instead of adding the shadow in code. You can also use resizableImageWithCapInsets to make for the various possible sizes of your buttons.
UIImage* image = [UIImage imageNamed:#"button.png"];
UIEdgeInsets insets = UIEdgeInsetsMake(16, 6, 16, 6); // change the insets to fit your needs
image = [image resizableImageWithCapInsets:insets];
[button setBackgroundImage:image forState:UIControlStateNormal];
Documentation on resizableImageWithCapInsets can be found at https://developer.apple.com/library/ios/documentation/uikit/reference/uiimage_class/index.html#//apple_ref/occ/instm/UIImage/resizableImageWithCapInsets:
This solution may not be fit for you, depending on how your buttons look. But using images instead of adding the shadow from code should speed up your code.

Related

Add shadow to image in UIKit

I have this screen with an UIImageView (the image is a SF Symbol)
The user can see this icon clearly, white on black. But - if the background is different:
It may be hard to see it.
I want to add a black shadow to the image so the user can see it better.
I tried looking online but all I saw is how to add shadow to the image view box, and that is not what I need. I need the shadow around the icon itself and not around the box of the image.
Any ideas? thanks in advance
Try this:
imageView.layer.shadowColor = UIColor.black.cgColor
imageView.layer.shadowRadius = 3.0
imageView.layer.shadowOpacity = 1.0
imageView.layer.shadowOffset = CGSize(width: 4, height: 4)
imageView.layer.masksToBounds = false

Create shadow around a UIVisualEffectView without covering the whole view

Is it possible to create a shadow around a UIVisualView with UIBlurEffect without letting the UIVisualView get coloured by the shadow underneath?
I basically just want the shadow around the view but with this code the shadow will cover the whole view which darkens the whole view to much:
let borderPath = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 15, height: 15)).cgPath
shadowView.frame = view.bounds
shadowView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
shadowView.layer.shadowOpacity = 0.3
shadowView.layer.shadowRadius = 3.0
shadowView.backgroundColor = UIColor.clear
shadowView.layer.shadowPath = borderPath
shadowView.layer.shadowOffset = CGSize(width: 0, height: 0)
self.view.insertSubview(shadowView, at: 0)
let blurEffect = UIBlurEffect(style: .extraLight)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.frame = view.bounds
blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
blurView.clipsToBounds = true
blurView.layer.cornerRadius = 15
view.insertSubview(blurView, aboveSubview: shadowView)
EDIT.
I need to achieve the same thing as in Apple's Maps application. Where the draggable favourite view both uses the UIVisualEffectView and a shadow around its top, without interfering with the UIVisualEffectView's background.
See example screenshots:
Ok, so the problem was that my background in the underlying view was white. And with the UIBlurEffect .extraLight used on a background which is lighter than the BlurEffect the shadow beneath a UIVisualView appears darker than with a more vivid background.
Also described in this question:
Fix UIVisualEffectView extra light blur being gray on white background
UPDATE
I found a project explaining how to solve this on Github The solution involves creating a 9-part UIImage to represent the shadow. The creator also explains the underlying layers of the iOS 10 Maps.
So i'm trying to recreate the look of iOS 10 maps. I decided to attach the maps app in the simulator to the debugger to see what was going on...
Apple actually get around this by having a UIImage over the top of the content with the border and shadow. Not the most elegant way to do it but i'm going for the exact look so I'm going to take the exact approach.
I also grabbed the asset (using this) from the Maps app to save making your own one. Shame they only have #2x artwork in it though :/
I found in iOS 12, this is not a problem, the UIVisualEffectView ignore the shadow of underneath views, it just sees through the shadow, like the shadow not exist.
The trick is to not set the shadowPath of the layer. If it is set, the shadow is painted below the visual effect view and, in consequence, darkens the blurred view.
The documentation for shadowPath states:
If you specify a value for this property, the layer creates its shadow
using the specified path instead of the layer’s composited alpha
channel.
The "instead…" part is what we actually need: the shadow should be composed after rendering the content, based on what the content leaves transparent. Now you might be deterred from not setting a shadowPath because the documentation also states that an "explicit path usually improves rendering performance". However, I didn't see any issues in real life so far. In comparison to the rendering cost of the UIVisualEffectView, painting the shadow doesn't make a difference, I assume.
Also make sure to set the shadow on a superview of the UIVisualEffectView, not on a sibling view.
I usually solve this kind of situation using composition of views. Instead of setting the shadow in the target view, I create a ShadowView and I put it behind the target view, both views with the same frame.
I've posted and example and code in this question.
An example of the result of this approach is the following:

iOS: Solid Border Outside UIView

A layer's borderWidth and borderColor properties draw a border inside the view. Remykits pointed this out here.
A layer's shadow... properties cannot be used to create a border that both appears on all four sides and is opaque for reasons I showed here.
What I failed to specify in that question (and the reason I've opened a new one) is that I want the border to be outside the view. Increasing the frame of the view to compensate for the space lost, as has been suggested, doesn't work; I'm using a UIImageView, so even if the frame is increased, the image is still cropped.
Another suggestion was to change the contentMode of the UIImageView to .Center, in combination with changing the size of the view, but this doesn't work as the view then isn't the proper size.
The solution I first thought of was to create another UIView "behind" this UIImageView, and give it a backgroundColor to mimic the effect of a border. I also thought of creating a custom subclass of UImageView. Both courses of action, however, involve making calculations based on the frame of the view. I've had many problems with the frame not being set by AutoLayout at the proper time, etc.
Other things that come to mind are digitally adding a border to the image or positioning the image in a specific part of the UIImageView. (My attempt at the latter was imageView.layer.contentsRect = CGRectInset(imageView.bounds, 4, 4), which resulted in a strangely pixellated image.)
To be clear, what I'm looking for is this:
It really feels like there should be a simpler way to do this than creating a new class or view. Any help appreciated.
Aha! Stitching together aykutt's comment about resizing the image and changing the conentMode, Paul Lynch's answer about resizing images, and rene's (life-saving) answer about what to do your subviews actually aren't laid out in viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.myContainer.setNeedsLayout()
self.myContainer.layoutIfNeeded()
var width: CGFloat = 4 //the same width used for the border of the imageView
var rect = CGRectInset(imageView.bounds, width, width)
var size = CGSizeMake(rect.width, rect.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
image.drawInRect(CGRectMake(0, 0, size.width, size.height))
var new = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.imageView.contentMode = .Center
self.imageView.image = new
}

how to implement this animation in iOS?

I am not sure about how to present this question because I don't know the animation term that I should use.
I need to know about this tree presentation animation. As it is appearing form root to top.
Please take a look on attached .gif file and let me know if anyone know about this animation or if you can guide me with example.
I will really appreciate.
Thanks for your time.
The best option for you is to split up the gif into multiple images and then do the following:
NSArray *gifImagesArray = [NSArray arrayWithObjects:imageOne, imageTwo, imageThree, nil];
imageView.animationImages = animateImagesArray;
imageView.animationRepeatCount = 1;
imageView.animationDuration = 1.0f;
[imageView startAnimating];
Edit following comments:
If you can't use multiple images you have to write the animation yourself.
One suggestion is to add a circular mask to the UIImage and animate it's removal.
This link explains how to draw a circular CALayer: Circular Progress Bars in IOS
This one here will show you how to create a mask on a UIImage: Simply mask a UIView with a rectangle
now all you have to do is a simple animation.
I have a solution that will work for this problem.
We can use gif image and convert it into UIImage object. So image object will work same as animation.
Thanks all for your answers.
In my opinion, I will separate this animation to 4 parts:
animation to show full black tree from center.
animation to show Texts
animation to show blue leafs.
animation to show 2 buttons at bottom.
You can use this framework https://github.com/facebook/pop to operate all animations step by step or even Core Animation.
Please do more research... I think you will success to make one.
You can achieve this animation using simple UIImageView, that can load multiple images using animationImages property of UIImageView.
First, create one NSMutablerArray of images.
then asssign that images to imageView, and animationDuration for that images.
UIImageView *animationImageView = [[UIImageView alloc] initWithFrame:CGRectMake(60, 95, 86, 193)];
animationImageView.animationImages = images;
animationImageView.animationDuration = 0.5;
add the imageview to view.
[self.view addSubview:animationImageView];
[animationImageView startAnimating];

UIImage's resizableImageWithCapInsets places a shadow on image

I'm having trouble customising an UISegmentedControl: I've subclassed it I'm setting it's background for both the selected state and the unselected state like this:
#define kEdgeInsets UIEdgeInsetsMake(18, 18, 18, 18)
UIImage *grayImage = [[UIImage v_imageNamed:#"gray_rect"] resizableImageWithCapInsets:kEdgeInsets];
[self setBackgroundImage:grayImage
forState:UIControlStateNormal
barMetrics:UIBarMetricsDefault];
UIImage *greenImage = [[UIImage v_imageNamed:#"green_rect"] resizableImageWithCapInsets:kEdgeInsets];
[self setBackgroundImage:greenImage
forState:UIControlStateSelected
barMetrics:UIBarMetricsDefault];
[self setTintColor:[UIColor colorWithRed:0.506 green:0.514 blue:0.525 alpha:1.000]];
Where this are the PNGs I'm using
Now, when I execute this code, I get a weird shadow on the segmented control, which is not what we want. This is what the output looks like
Which is very weird, because there is no shadow on the original images, nor does UISegmentedControl add one (as far as I know).
Further checking, I noticed that if I removed the resizableImageWithCapInsets: call, the image looks distorted (as one should expect) but without the shadow.
Any ideas? because I'm literally going mad over this, since I don't have this problem when using resizableImageWithCapInsets: with UIButton
Thanks a lot!
I figured it out.
Turns out the segmented controller's frame is of 44pts height, and the background images was 75ptsx75pts. Since i've set the image's top and bottom inset to 18pts, the OS was taking the image's top and bottom 18pts and resizing it, ignoring the rest. Here is the fun part, since the image has a gradient, the rest of the image is ignored and it the control is colored like that.
In order to use images with vertical gradients you must use a base PNG with the exact same height as your control (http://useyourloaf.com/blog/2012/07/05/customizing-appearance-with-resizable-images.html)
This is what I did, resize the image to 44x44pts and change the insets to UIEdgeInsetsMake(0, 10, 0, 10) denoting that the image can't have to be resized vertically

Resources