How to mask UIViews in iOS - ios

I've seen similar questions, but haven't found workable answers.
I want to mask a UIView using a grey image (need to convert to alpha scale for masking). The UIView has background. It should be easy to mask an image, but I want to mask any UIView.
Any clues will be appreciated.

I've been working on this problem for a couple of hours and have a solution that I think will do what you want. First, create your masking image using whatever means you see fit. Note that we only need the alpha values here, all other colours will be ignored, so make certain that the method you use supports alpha values. In this example I'm loading from a .png file, but don't try it with .jpg files as they don't have alpha values.
Next, create a new layer, assign your mask to its contents and set this new layer to your UIView's own layer, like so: you should find that this masks the UIView and all its attached subviews:
UIImage *_maskingImage = [UIImage imageNamed:#"mask"];
CALayer *_maskingLayer = [CALayer layer];
_maskingLayer.frame = theView.bounds;
[_maskingLayer setContents:(id)[_maskingImage CGImage]];
[theView.layer setMask:_maskingLayer];
With this done, you can set the UIView's background colour to whatever you like and the mask will be used to create a coloured filter.
EDIT: As of iOS8 you can now mask a view simply by assigning another view to its maskView property. The general rules stay the same in that the maskView's alpha layer is used to determine the opacity of the view it is applied to.

For apps targeting iOS 8.0+ this worked well (in this case, using a gradient as the mask) It avoids any need to resize or position the mask.
// Add gradient mask to view
func addGradientMask(targetView: UIView)
{
let gradientMask = CAGradientLayer()
gradientMask.frame = targetView.bounds
gradientMask.colors = [UIColor.blackColor().CGColor, UIColor.clearColor().CGColor]
gradientMask.locations = [0.8, 1.0]
let maskView: UIView = UIView()
maskView.layer.addSublayer(gradientMask)
targetView.maskView = maskView
}
In my case, I want to remove the mask once the user starts scrolling. This is done with:
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
exerDetailsTableView.maskView = nil
}
where the view is defined as an #IBOutlet:
#IBOutlet weak var exerDetailsTableView: UITableView!
Result:

I don't know the exact code off the top of my head but the basic idea is to have two UIViews. One UIView would have it's image property set to be the grey scale image and the other UIView would be set as usual the only difference is that you would position the initial UIView directly on top of the UIView containing the "normal" image.
I hope that is enough to push your idea a step further.

Related

IOS: Cannot Change Color of CAShapeLayer

I want to create a layer to act as a mask for my UIImageView which was created in interface builder. However, no matter what I do the layer remains white. The code is pretty straightforward any ideas what is causing this behavior ?
UIImage* image = [UIImage imageNamed:#"face"];
self.imageView.image = image;
CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
maskLayer.frame = self.imageView.bounds;
maskLayer.fillColor = [[UIColor blackColor] CGColor];
maskLayer.path = CGPathCreateWithRect(self.imageView.bounds, NULL);
self.imageView.layer.mask = maskLayer;
self.maskLayer = maskLayer;
I edited the code and added the path but it still does not work.
You seem to have a misconception as to what a mask is. It is a set of instructions, in effect, for injecting transparency into a view, thus "punching a hole" through the view to a greater or lesser extent (depending on the degree of transparency). It has no color. You are seeing white because that is the color of what is behind your image view — you have punched a hole through the entire image view and made it invisible.
From the docs:
The layer’s alpha channel determines how much of the layer’s content
and background shows through. Fully or partially opaque pixels allow
the underlying content to show through but fully transparent pixels
block that content.
Black fill (interior of the closed path) = opaque pixels
Area outside of the closed path = transparent pixels
Image will appear on the interior region of your path and not appear outside of that path. The actual colour is irrelevant: when you set a layer as a mask, all we are interested in now is opacity.
The path is ostensively the same size as the imageView, so you won't see any difference as the mask matches the imageView bounds. Additionally , if you use this code before the geometry is fully set, such as in viewDidLoad, you may not get the results you expect.
As matt suggests - you need to think what result you are after.
If you want a "black translucent color" - or similar - consider adding another translucent-colored layer to the mix. But this is not a mask.

What do I need for masking a UIImageView and how do I do it in Swift 3?

I was wondering what I would need if I wanted to use a mask image to get my UIImageView in a specific shape. From what I understand, to create a mask, I need to have an image with the shape of the mask all black on top of a white background. Something like this, for example:
First of all, is this sufficient to shape an image view, and if so, how do I do it in Swift 3? I can only find masking code that is either outdated or written in Objective-C. I've tried simply assigning the image above to an UIImageView and then assign the image view to the mask property of the UIImageView I want to shape, like so:
self.defaultImageView.mask = self.maskImageView
This didn't do anything. It just made self.maskImageView disappear (both image view's added through the storyboard and connected using IBOutlet properties). I'm sure I'm forgetting to do something. It can't be this simple. I would appreciate it if someone could help me out. Like I said, I put both image views on the exact same spot, on top of each other, in the storyboard.
UPDATE:
My first attempt to set the mask programmatically after deleting it from my storyboard.
let layer:CALayer = CALayer()
let mask:UIImage = UIImage(named: "Black-Star-Photographic-Agency")!
layer.contents = mask
layer.frame = CGRect(x: 0, y: 0, width: ((self.defaultImageView.image?.size.width)!), height: (self.defaultImageView.image?.size.height)!)
self.defaultImageView.layer.mask = layer
self.defaultImageView.layer.masksToBounds = true
The result was that the image view had completely disappeared and wasn't visible anymore. Am I doing something, am I forgetting something or both?
You should use a png image, which supports transparency, unlike jpg.
In Photoshop your image should look similar to this:
It doesn't matter if your shape is black or white. What matters is transparency of each pixel. Opaque area (black in this case) will be visible and transparent area will get trimmed.
Edit:
You should not create mask view from storyboard if you do so. It is not going to be a part of your view hierarchy. Just add it programmatically like this:
let maskView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
maskView.image = UIImage(named: "mask")
imageView.mask = maskView
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
maskView.frame = imageView.bounds
}
Output:
Here is a test project to show how it's working.
Also if you're using a custom frame/image and run into the mask not showing properly, try setting the content mode of the mask:
maskView.contentMode = .scaleAspectFit

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
}

Make a white label visible on top of bright image view

Basic issue:
I believe the trick is with masking, but I am not able to get a good hold of how this is set.
Basically I have a bright image (set to a uiimageview object), and I have a label at very bottom (which is added on top of the image view) needs a well readable white text on it. Right now, the white text is hard to read (because of the bright background).
What I am doing:
I am setting a mask for the image view with something like
http://cl.ly/image/0i0N1p271d42
maskContainer = [CALayer layer];
UIImage *maskImg = [UIImage imageNamed:#"mask_profile"];
[maskContainer setContents:(id)[maskImg CGImage]];
CGRect frma = maskContainer.frame;
frma.size.width = self.frame.size.width;
frma.size.height = self.frame.size.height;
maskContainer.frame = frma;
[self.imageView.layer setMask:maskContainer];
Its messed up. The overall image starts fading on top.
Can anyone share their insight on the right way to mask?
You could set a drop shadow on your text to make is stand out even over a white background:
myLabel.layer.shadowOpacity = 0.8f;
myLabel.layer.shadowOffset = CGSizeMake(0, 0);
Easiest option is to adjust the alpha on the UILabel to the desired darkness in order to make the text stand out. If you do not want to hide the image and the image itself serves as a dark background, then set the alpha on the label to 0.
The best way to do this is to place the label in a uiview then create a gradient to apply as the background to the uiview. You can create the gradient as either an image with transparency or you can draw it in code. This will create a darkening effect on you bright image just behind the label so the text will pop.

Is it possible to place a CAGradientLayer under the background layer?

I'm adding a gradient layer to a custom button, the this reference in the following code:
//a custom gradient
var layerGradient = new CAGradientLayer();
//the gradient colors are the base color to the modified version
layerGradient.Colors = new CGColor[]{color.CGColor, color2.CGColor};
//add the gradient as a sublayer in the button
this.Layer.InsertSublayer(layerGradient, 0);
This works fine and the gradient does what it should. But, if the button has a background image, the background doesn't display. The gradient appears to be on a layer above the background, obscuring it. Is there any way to fix this, or do I need to draw the background on my own layer and add it manually?
Haven't tried it but possibly insertSublayer:below: is what you are looking for.
- (void)insertSublayer:(CALayer *)aLayer below:(CALayer *)sublayer
I solved this by adding the background image as its own layer, and then adding the gradient layer below it:
mImageView = new UIImageView();
this.Layer.InsertSublayer(mImageView.Layer, 0);
mImageView.Image = mImage;
So this makes a UIImageView layer to hold the image. And then I call the code as in my question to insert the gradient layer underneath this ImageView layer and it works.

Resources