multiple punch-out style mask? - ios

I've done simple CALayer masks before but I think I'm getting confused on what they do. I'm trying to have a punch out effect with several (2) views.
Here's what I have so far. I'm looking to have a white square with punched out label and image (so you can see the brown background through it. Where am I going wrong?
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor brownColor];
self.viewToPunch = [[UIView alloc] initWithFrame:CGRectZero];
[self.view addSubview:self.viewToPunch];
self.viewToPunch.backgroundColor = [UIColor whiteColor];
self.punchLabel = [[UILabel alloc] initWithFrame:CGRectZero];
self.punchLabel.textColor = [UIColor blackColor];
self.punchLabel.font = [UIFont boldSystemFontOfSize:20.0];
self.punchLabel.text = #"punch";
self.punchLabel.textAlignment = NSTextAlignmentCenter;
self.punchImage = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:#"plus"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
[self.punchImage setContentMode:UIViewContentModeCenter];
self.viewsToPunch = #[self.punchLabel,self.punchImage];
[self punch:self.viewToPunch withUIViews:self.viewsToPunch];
}
- (void)punch:(UIView *) viewToPunch withUIViews:(NSArray *)viewsToPunch
{
CALayer *punchMask = [CALayer layer];
punchMask.frame = viewToPunch.frame;
NSMutableArray *sublayers = [[NSMutableArray alloc] init];
for (UIView *views in viewsToPunch){
[sublayers addObject:views.layer];
}
punchMask.sublayers = sublayers;
punchMask.masksToBounds = YES;
viewToPunch.layer.mask = punchMask;
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
self.viewToPunch.frame = CGRectMake(50, 50, 100, 100);
self.punchLabel.frame = CGRectMake(0, 0, 100, 100);
self.punchImage.frame = CGRectMake(0, 0, self.viewToPunch.frame.size.width, 40.);
[self punch:self.viewToPunch withUIViews:self.viewsToPunch];
}
So not only do the frames seem to be off, it seems to be the opposite of a punch out. How do I invert the mask and fix up the frames?
Thanks a lot for any help! I put it in a method punch:withUIViews: so I can hopefully reuse it in other areas.

When you apply a mask to a CALayer, it only gets drawn in the parts where the mask is not transparent. But you're simply applying an empty (transparent) mask, with the wrong coordinates (which is why your view isn't completely transparent: the mask isn't covering the view completely; it should be punchMask.frame = viewToPunch.bounds;)
You might want to look into CAShapeLayer and assign it a path. Use that as mask layer.
For example, see CAShapeLayer mask view or Getting Creative with CALayer Masks (cached blog post).

I tried to combine mask of CAGradationLayer and CAShapeLayer, and It is possible.
https://stackoverflow.com/a/32220792/3276863

Related

draw a circle of rectangles in Xcode

I have tried this code to draw a circle of rectangle
incNumberDefault=0;
for (int i = 0; i < maxNumber; i++) {
incNumberDefault = incNumberDefault +5;
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(incNumberDefault, 100, 3, 15)];
myView.center = CGPointMake(130,130);
myView.transform = CGAffineTransformMakeRotation(DegreesToRadians(incNumberDefault));
myView.backgroundColor = [UIColor lightGrayColor];
[self.speedDisplay addSubview:myView];
}
the outputs seems close circle where as i need transparent or space in middle of circle.
You can set UIView cornerRadius to half of the UIView width or height like,
myView.layer.cornerRadius=myview.frame.size.width / 2; or
myView.layer.cornerRadius=myview.frame.size.height / 2
For that you have to give same width and height to your UIView.
Try this :
// initialize the view with same height and width for perfect circle.
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 15, 15)];
// round the corner of view with required radius.
myView.layer.cornerRadius = myView.frame.size.width/2; // height can also be used
// circle with space in middle.
myView.backgroundColor = [UIColor clearColor];
myView.layer.borderColor = [UIColor lightGrayColor].CGColor;
myView.layer.borderWidth = 2.0f;
[self.speedDisplay addSubview:myView];
Hope it will solve your problem.
Thank you for your answer but i want to develop a dashed circle in certain distance and i have tired this code :
incNumberDefault=0;
for (int i = 0; i < maxNumber; i++) {
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(100+60 * sin(incNumberDefault), 100+60*cos(incNumberDefault), 3, 15)];
//myView.transform = CGAffineTransformMakeRotation(DegreesToRadians(incNumberDefault));
myView.backgroundColor = [UIColor lightGrayColor];
[self.speedDisplay addSubview:myView];
incNumberDefault = incNumberDefault +9;
}
but it generate random rectangle in a circle.
I want it in sequence.
Thank you.

Masking Content of a UIView

Is it possible to mask certain area of a view, so that content of that part became invisible/hidden.
I am using a web view and wanted to remove the top round corner of view, like below attached image -
Problem -
I want to remove/hide all the content which is below red area.
What I have tried -
I have tried to add a image view on top of web view and added a masking layer on top of it, but that doesn't seems to be working for me -
UIImageView *maskImage = [[UIImageView alloc] init];
maskImage.frame = CGRectMake(256, -10, 64, 64);
maskImage.image = [UIImage imageNamed:#"maskingImage"];
[self.webView addSubview: maskImage];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
CGRect maskRect = whitefoldMaskImage.frame;//CGRectMake(0, 0, 50, 100);
CGPathRef path = CGPathCreateWithRect(maskRect, NULL);
maskLayer.path = path;
CGPathRelease(path);
maskImage.layer.mask = maskLayer;
EDIT 1 --
As per suggestion of Mundi, I have tried opaque view on top of web view.
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(256, -10, 64, 64)];
view.opaque = YES;
[self.webView addSubview:view];
But that haven't worked too.
I have also gone through following threads on SO, but haven't found any solution -
CALayer: add a border only at one side
Simply mask a UIView with a rectangle
I want to remove/hide all the content which is below red area.
Simply add a opaque view above the area you want to hide. No need for a mask. E.g.
UIView *cover = [[UIView alloc] initWithFrame:topRightCornerFrame];
cover.backgroundColor = [UIColor whiteColor];
[webView addSubView:cover];

CALayer gets redrawn frequently despite shouldRasterize --> why?

I have some fairly complex layers in a part of my app and I want them to rasterize as their content changes close to never. The frame of the view these layers are in can be changed by dragging a "spacerbar" around with your finger.
I made a simple test-app to visualise my problem and show you some code:
#interface ViewController () {
UIView* m_MainView;
UIView* m_TopView;
UIView* m_BottomView;
UIView* m_MidView;
int m_Position;
}
#end
#implementation ViewController
- (void)loadView {
m_MainView = [[UIView alloc] init];
m_TopView = [[UIView alloc] init];
m_TopView.backgroundColor = UIColor.blueColor;
m_TopView.translatesAutoresizingMaskIntoConstraints = false;
m_BottomView = [[UIView alloc] init];
m_BottomView.backgroundColor = UIColor.blueColor;
m_BottomView.translatesAutoresizingMaskIntoConstraints = false;
m_MidView = [[UIView alloc] init];
m_MidView.backgroundColor = UIColor.blackColor;
m_MidView.translatesAutoresizingMaskIntoConstraints = false;
[m_MainView addSubview:m_TopView];
[m_MainView addSubview:m_BottomView];
[m_MainView addSubview:m_MidView];
CALayer* targetLayer = [[CALayer alloc] init];
targetLayer.frame = CGRectMake (100, 200, 500, 50);
targetLayer.backgroundColor = UIColor.yellowColor.CGColor;
targetLayer.shouldRasterize = true;
[m_BottomView.layer addSublayer:targetLayer];
UIPanGestureRecognizer* recognizer = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:#selector(handlePan:)];
[m_MainView addGestureRecognizer:recognizer];
m_Position = 400;
super.view = m_MainView;
}
- (void)handlePan:(UIPanGestureRecognizer*)sender {
float touchY = [sender locationInView:m_MainView].y;
m_Position = (int)touchY;
[m_MainView setNeedsLayout];
}
- (void)viewWillLayoutSubviews {
CGRect bounds = super.view.bounds;
m_TopView.frame = CGRectMake (0, 0, bounds.size.width, m_Position - 10);
m_MidView.frame = CGRectMake (0, m_Position - 10, bounds.size.width, 20);
m_BottomView.frame = CGRectMake (0, m_Position + 10, bounds.size.width,
bounds.size.height - m_Position - 10);
}
#end
.
Now ... By using instruments and activating "Color Hits Green and Misses Red" one can see when a layer is redrawn (is then highlighted in red).
In this example the bar (targetLayer) is green (cached) most of the time, but if I start dragging the spacer (which in fact changes the frame of the bottom-view) the layer turns red some times ... especially if I reverse the direction of the drag or if I drag slowly.
In my app this causes flickering as redrawing these layers is expensive.
Why does this happen??
I never change any property of the layer but it gets redrawn anyways?
I am sure I am missing something :-)
As a workaround I could make a snapshot of the layers and use the image ... that would work I think and if there is no solution I will have to use this approach but I would like to understand what is the problem here.
Anyone?

How can I access the standard viewForHeaderInSection for a tableView?

I've got an indexed UITableView with individual sections. I would like to use a different background color for the header views in each section. I know I can completely roll my own view by implementing tableView:viewForHeaderInSection: (for example, see question # 2898361), but that seems to be "too much work" to me - the standard view looks fine, I would just have to change its background color.
But how do I access this standard view? I can't use [super tableView:viewForHeaderInSection:] because this is a question of implementing a protocol and not an issue of inheritance. Any other way I can get the standard view?
I'm almost certain you can't do this easily. I used one of my tech support request on my dev account recently asking about altering the background and borders of UITableView sections. The apple engineer told me that this really wasn't an easy thing to do, and even if you managed to do it, you would probably affect performance. He also pointed me to cocoawithlove and an article about editing uitableviews:
http://cocoawithlove.com/2009/08/adding-shadow-effects-to-uitableview.html
Really, creating your own header isn't that much effort. Below is some code I pulled out of one of my projects - it was commented out, so might not work straight away - but you can get the idea:
- (CAGradientLayer *) greyGradient {
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.startPoint = CGPointMake(0.5, 0.0);
gradient.endPoint = CGPointMake(0.5, 1.0);
UIColor *color1 = [UIColor colorWithRed:255.0f/255.0f green:255.0f/255.0f blue:255.0f/255.0f alpha:1.0];
UIColor *color2 = [UIColor colorWithRed:240.0f/255.0f green:240.0f/255.0f blue:240.0f/255.0f alpha:1.0];
[gradient setColors:[NSArray arrayWithObjects:(id)color1.CGColor, (id)color2.CGColor, nil]];
return gradient;
}
- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
CGFloat width = CGRectGetWidth(tableView.bounds);
CGFloat height = [self tableView:tableView heightForHeaderInSection:section];
UIView *container = [[[UIView alloc] initWithFrame:CGRectMake(0,0,width,height)] autorelease];
container.layer.borderColor = [UIColor grayColor].CGColor;
container.layer.borderWidth = 1.0f;
CAGradientLayer *gradient = [self greyGradient];
gradient.frame = container.bounds;
[container.layer addSublayer:gradient];
UILabel *headerLabel = [[[UILabel alloc] initWithFrame:CGRectMake(12,0,width,height)] autorelease];
headerLabel.backgroundColor = [UIColor clearColor];
headerLabel.font= [UIFont boldSystemFontOfSize:19.0f];
headerLabel.shadowOffset = CGSizeMake(1, 1);
headerLabel.textColor = [UIColor whiteColor];
headerLabel.shadowColor = [UIColor darkGrayColor];
NSString *title = [self tableView:tableView titleForHeaderInSection:section];
headerLabel.text = title;
return container;
}
Make sure to
#import <QuartzCore/QuartzCore.h>
By the way... this isn't supposed to mimic the look of the standard headers - its just an example. But I'm sure with a bit of trial and error you could alter this to mimic the standard ones and then change the colors slightly.
Although the other answers correctly point out you cannot access the default view to make simple modifications to it, if you have nothing to customize for a particular section header, you can return nil from tableView:viewForHeaderInSection: and the table view will use the default view.
This is helpful if you only need to customize some of your headers.
For whatever reason this is undocumented.
There is one problem with #bandejapalsa solution: the previous cell's separator is still visible with this implementation where as it is not on the default iOS sectionHeaderView. The solution I found was to use a CALayer and offset it by 1 pix. The image needs to be 1pix taller than the view frame itself.
// Create the view for the header
CGRect aFrame =CGRectMake(0, 0, tableView.contentSize.width, IMIUICustomisation.sectionHeaderViewHeight);
UIView * aView = [[UIView alloc] initWithFrame:aFrame];
aView.backgroundColor = UIColor.clearColor;
// Create a stretchable image for the background that emulates the default gradient, only in green
UIImage *viewBackgroundImage = [[UIImage imageNamed:#"greenheader.png"] stretchableImageWithLeftCapWidth:12 topCapHeight:0];
// Cannot set this image directly as the background of the cell because
// the background needs to be offset by 1pix at the top to cover the previous cell border (Alex Deplov's requirement ^_^)
CALayer *backgroungLayer = [CALayer layer];
backgroungLayer.frame = CGRectMake(0, -1, tableView.contentSize.width, IMIUICustomisation.sectionHeaderViewHeight+1);
backgroungLayer.contents = (id) viewBackgroundImage.CGImage;
backgroungLayer.masksToBounds = NO;
backgroungLayer.opacity = 0.9;
[aView.layer addSublayer:backgroungLayer];
// Take care of the section title now
UILabel *aTitle = [[UILabel alloc] initWithFrame: CGRectMake(10, 0, aView.bounds.size.width-10, aView.bounds.size.height)];
aTitle.text = [delegate tableView:tableView titleForHeaderInSection:section];
aTitle.backgroundColor = UIColor.clearColor;
aTitle.font = [UIFont boldSystemFontOfSize:18];
aTitle.textColor = UIColor.whiteColor;
// Text shadow
aTitle.layer.shadowOffset = CGSizeMake(0, 1);
aTitle.layer.shadowRadius = .2;
aTitle.layer.masksToBounds = NO;
aTitle.layer.shadowOpacity = 0.5;
aTitle.layer.shadowColor = IMIUICustomisation.selectedElementTextShadowColor.CGColor;
[aView addSubview:aTitle];
return aView;

Creating a custom progress bar with images

Pretty much what I am trying to do, is create the following image:
So this is a progress bar, which will show how many votes the client get in that option. How can I do that? I was wondering if I can use a mask (as I would in flex), but all the masking implementations that I found on the web are doing masks for UIImages, and not for UIImageView. I cannot do the mask in the UIImage, because the two images have the same dimensions. I have to use the X and Y of the images to crop them?! So, any suggestions?
Here are the two images that I have to create that progress bar:
Cheers.
you want to get rid of all of the same bit in the middle of each image.
then do something like:
- (void)viewDidLoad
{
[super viewDidLoad];
[self addProgressBarInFrame:CGRectMake(20.f, 20.f, 280.f, 50.f) withProgress:.9f];
[self addProgressBarInFrame:CGRectMake(20.f, 100.f, 200.f, 25.f) withProgress:.1f];
}
-(void)addProgressBarInFrame:(CGRect)frame withProgress:(CGFloat)progress
{
float widthOfJaggedBit = 4.0f;
UIImage * imageA= [[UIImage imageNamed:#"imageA"] stretchableImageWithLeftCapWidth:widthOfJaggedBit topCapHeight:0.0f];
UIImage * imageB= [[UIImage imageNamed:#"imageB"] stretchableImageWithLeftCapWidth:widthOfJaggedBit topCapHeight:0.0f];
UIView * progressBar = [[UIView alloc] initWithFrame:frame];
progressBar.backgroundColor = [UIColor whiteColor];
UIImageView * imageViewA = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, frame.size.width*progress, frame.size.height)];
UIImageView * imageViewB = [[UIImageView alloc] initWithFrame:CGRectMake(frame.size.width*progress, 0.f, frame.size.width - (frame.size.width*progress), frame.size.height)];
imageViewA.image = imageA;
imageViewB.image = imageB;
// imageViewA.contentStretch = CGRectMake(widthOfJaggedBit, 0, imageA.size.width - 2*widthOfJaggedBit, imageA.size.height) ;
// imageViewB.contentStretch = CGRectMake(widthOfJaggedBit, 0, imageB.size.width - 2*widthOfJaggedBit, imageB.size.height) ;
[self.view addSubview:progressBar];
[progressBar addSubview:imageViewA];
[progressBar addSubview:imageViewB];
}
Grady Player's answer is great. Just a small note, for anyone that uses this. I tried to update the frames of the two UIImageView's to update the "progress". I tried to force an update with "setNeedsDisplay", but to no avail. (sizeToFit caused problems too).
Anyway, the only way I figured out how to update the progress of an existing progress bar was to call "setFrame:CGRectMake" with my new dimensions.

Resources