drawRect over subviews? - ios

I created a container view that holds a bunch of child views - a collection view, a custom toolbar and some bits and pieces.
The design has a border on the top, left and right sides, but not the bottom, so I overrode drawRect to include border.
When I added the toolbar I noticed that it appears over the top of the border. (For some reason I initially thought it wouldn't but of course it does!).
Is there anyway I can tell drawRect to draw over the top of my subviews?
Of course there's loads of other ways to solve my problem (adjust the toolbar's frame for example) however I'm asking this question in order to get a deep understanding of how drawing works in relation to compositing and the view hierarchy.

Drawing happens beneath all subviews of a UIView. Think of it as being on the very base - an actual part of your view - and then each subview is added on top of your view. To make the drawing above the subviews is the same as wanting for the subviews to appear under the view, while still being subviews. Perhaps that analogy makes it clearer why it must always be on the bottom. And it also leads you logically to the solution:
To get the drawing to appear above subviews, simply create a new UIView subclass to place the drawing code inside, and place this above all other subviews.
It might also be a good idea to override your UIView's addSubview: implementation, to ensure your subview always remains on top.

I believe you can't, since the drawRect is called first for the view and when it has finished drawing drawRect is called for subviews to draw over it. Maybe just make another subview on top of that view that has the borders you need and is transparent everywhere else?

Subviews are drawn on top of their super views. So the answer to your question is no.
At the time when you draw the border on your container view, Cocoa hasn't even started drawing the toolbar yet.
I guess you could make the top of the border a subview or move the toolbar down a bit.

Related

Drop a shadow behind multiple UIViews

In this view
there are multiple subviews. Now I configured the views to drop a shadow as shown in the screenshot: The views are dropping the shadow also over the other views. Do you see a way to easily drop the shadow behind all views? So z-index of all the shadows < z-index of all the views.
I already tried to create an additional view for each box and placed all of this shadow views behind all boxes. This kind of works. But there is a lack when moving the boxes around using a PanGesture. Also this design looks a it messy. At the moment, it's a clear design to have one UIView for each box.
You're thinking about the solution wrong. However in your own words, 'behind all views' is the answer.
As you noticed, applying a shadow to each view will have overlapping shadows due to each views z-index.
You mentioned creating an additional view for each box which is close to another solution but not quite. You could create a single container view and place all of the boxes inside this one container view. Then apply your shadow to the container view. This will give you your result, but will have horrible performance! Every time layoutSubviews is called, such as when a views frame changes, the shadows are recalculated. As you mentioned, you're using a pan gesture, so moving a box slightly will layout the subviews a few dozen times.
The solution you should go with is to create 2 container views which are sibling views. For every colored view you create, add it to the top container view and create another view which will be added to the bottom container view, lets call it the shadow container view. Use constraints on the views inside of the shadow container so their top, leading, bottom and trailing are equal to their equivalent view in the top container view. Next add a shadow to the views inside of the shadow container view and make sure to apply the layer.shadowPath! This shadow path is the key to prevent the shadow from being redrawn when you pan.
If you don't understand and need an example let me know and I'll give you one.
Hey Chris, Try this
yourView.layer.shadowColor = UIColor(white: 0.0, alpha: 0.5).CGColor
yourView.layer.shadowOffset = CGSizeMake(0.0, 0.0)
yourView.layer.shadowOpacity = 1.0
yourView.layer.shadowRadius = 6.0 //increase the width & height of shadow
You can simply add views with shadow first then add your actual view above them. Or, you can try to alter the zPosition of layer of all the view after setting shadows.

CAGradientLayer causes subviews not being visible

Hello Masters of iOS and Swift,
after two frustrating days, I desperately decided to ask here. The problem:
When adding a CAGradientLayer, the subviews aren't shown.
Details:
I have made a method to add a CAGradientLayer in an extension of
UIView
I simply call the method on any view and this itself works perfectly
But if I try to use this method for a UIView in a viewhierarchy (as a background) unfortunately all subviews aren't visible anymore, the
gradient seems to "overrender" all subviews
if I don't call the "addGradient" method on the container view, all subviews are shown properly
amazing detail: Although the subviews aren't visible, they are somehow present and "active" (e.g. a "invisible" UIButton fires")
I am using Autolayout
Any idea would be appreciated!
Thanks in advance.
LukeSideWalker,
Not sure but you can always try to add your layer below all other layer so it wont cover existing subViews. Try this, in your extension where you add layer to view
self.layer.insertSublayer(layer, atIndex: 0)
Tried adding ImageView as subView worked fine :) Should solve your problem as well.
I encountered a similar problem before and resolved the issue by adding a background view to my view. So the view hierarchy would be like:
view
backgroundView
someView
someViewInsideView
someOtherView
Top views alpha seems to affect subview's alpha too. So I use a view with a clear background colour and use backgroundView to give view's background color and other property's with alpha modifiers.

Setting the click area of a custom UIView

Is it possible to increase the clickable area of a UIView subclass?
I would rather do it without subclassing it's superview. Is that possible?
I've seen how to do it by overriding hitTest:withevent: and pointInside:withEvent: on the superview, but as I said, I would rather avoid that.
Why not create a container view that has a larger area, with a transparent background, that contains a subview that has your visible part? This is a quick and easy way to increase the tap area of a button, for instance.

Making an element an element display on top of the UIView border

I am trying to place a UIButton inbetween my popup view and the parent view.
I cant successfully do that by doing that [self.view addSubview:new];. My problem there is the border UIView can be seen across the UIButton .
I've tried [self.view.superview addSubview:new]; thinking that that would make it go away but it doesnt it still shows there.
I need to find a way to successfully place that button on top of everything (UIView border in this case).
I know I could do that if I insert the button from the parentView, but I want to handle all my subViews buttons within each subView, otherwise it everything will become messy very quickly.
Is there a way to do what I am trying to achieve?
According to Apple's CALayer documentation, borders always appear above subviews because they're drawn on another layer. The best solution is to create a background view to fake the border.
So instead, your popup view would have an orange background. It'd have another, slightly smaller subview directly over it with a white background, and then your button.
See this post for the implementation.

UIView drop shadow on only one other view

There are a lot of questions about how to draw a drop shadow for a view. But how can I have my view cast a shadow that is received only by one other view?
If view A is the view with the shadow and view B is the parent of A, you can set masksToBounds which is a property of B's layer to YES.
Contents of B and its subviews will not be shown outside of B. They will be clipped.
More detail here: What UIView layer.masksToBounds is doing if set to YES?
However, if you want the shadow drops on more complex scene, you might need to be a little more specific.
Make an app with a white background.
Make your view plus shadow.
Take a screen shot.
Use the screen shot in your original app.
Of course you could also make the picture on the fly in code, but that is a bit more challenging. In any case, it would be code you do not need in your final app.

Resources