I have a view with has another view (called swipeView) inside it. I'm trying to gradient color it using the CAGradientLayer - the code is below
CAGradientLayer *layer = [CAGradientLayer layer];
layer.colors = [NSArray arrayWithObjects:(id)[UIColor darkGrayColor].CGColor,
[UIColor lightGrayColor].CGColor, nil];
layer.frame = swipeView.bounds;
[swipeView.layer insertSublayer:layer atIndex:0];
I'm calling this in the ViewDidLoad method of the main view controller. I see no effect - the view I'm trying to color remains just the same. What am I doing wrong? Are there any other settings/ordering etc. I should know about?
I don't know if this is the problem you're having, but I was just having the exact same symptom, and after a few hours of bafflement, I discovered my problem: I was creating a button, adding the gradient layer, and only later positioning the button. Initially, the button dimensions are all 0, and when setting layer.frame = swipeView.bounds; I was setting the gradient layer to have 0 dimensions too. I added code so that when sizing the button, I also sized the sublayers to match, and this fixed the problem. I'm actually using MonoTouch, so the code looks like this:
btn.Frame = new RectangleF([the dimensions you need]);
foreach(var s in btn.Layer.Sublayers)
{
s.Frame = btn.Bounds;
}
Maybe you're creating a view in code, and the view hasn't been sized or positioned, and so when you add the gradient it also has no size, and that's why you can't see it. When the view changes size (in ViewDidLayoutSubviews maybe), resize the gradient layer and see if that solves it.
That should work. Are you sure you hooked up your swipeView outlet correctly? Debug your -viewDidLoad and verify that swipeView is not nil. Also double check that the bounds (layer.frame) is something sensible.
It shouldn't be necessary, but you can try throwing in a [layer setNeedsDisplay] just to force the layer to render again, but I don't think that is your issue here.
CAGradientLayer *layer = [CAGradientLayer layer];
layer.colors = [NSArray arrayWithObjects:(id)[UIColor darkGrayColor].CGColor,
(id)[UIColor lightGrayColor].CGColor, nil];
layer.frame = swipeView.bounds;
[swipeView.layer insertSublayer:layer atIndex:0];
Make sure that swipeView is not nil and try swipeView.frame.
You don't need the nil terminator at the end of the colors array.
Also, I found insertSublayer did not work, with index either 0 or 1. Changing to simply addSublayer did the trick.
Related
I am trying to make a drawing application following this tutorial: http://www.effectiveui.com/blog/2011/12/02/how-to-build-a-simple-painting-app-for-ios/, and then I tried to make it such that I don't only draw on the entire screen, I only draw on a UIView that is inside of another UIView. (What I call a nested UIView)
My code is currently on github: https://github.com/sammy0025/SimplePaint
The only parts I tweaked with the original code from the tutorial is to change some class prefix names, enabled ARC so no deallocs, used storyboards (which works fine with the original code from the tutorial), and change this code in the main view controller implementation file (SPViewController.m):
-(void)viewDidLoad
{
[super viewDidLoad];
nestedView.backgroundColor=[UIColor colorWithWhite:0 alpha:0]; //This is to make the nested view transparent
SPView *paint = [[SPView alloc] initWithFrame:nestedView.bounds]; //original code is SPView *paint=[[SPView alloc] initWithFrame:self.view.bounds];
[nestedView addSubview:paint]; //original code is [self.view addSubview:paint];
}
My question is how do I make sure that I only draw inside the nested UIView?
Add clipping to your sub view. E.g. self.bounds would cover the whole area of a view.
UIBezierPath *p = [UIBezierPath bezierPathWithRect:self.bounds];
[p addClip];
I believe clipping for a view should be based on bounds initially and that you may shrink it by adding an new clipping. But there might be a difference between iOS and OS X here.
Rather than using an SPView inside a nested view, consider just changing the frame of the SPView to match what you want. This should solve your problem of only allowing drawing within a given rect.
I think you're seeing issues because of your storyboard configuration. I dont know much about storyboard, but I was able to get this to work programmatically by throwing out storyboard's view and building my own in viewDidAppear.
-(void)viewDidAppear:(BOOL)animated{
UIView *newView = [[UIView alloc] initWithFrame:self.view.bounds];
newView.backgroundColor = [UIColor greenColor];
SPView *newPaint = [[SPView alloc] initWithFrame:CGRectInset(self.view.bounds, 40,40)];
[newView addSubview:newPaint];
self.view = newView;
}
I have a UITableViewController that I'm trying to custom style. I've got 95% of the way there, but am having trouble removing an odd shading from the cell background. I've highlighted what I'm trying to remove in this screen shot:
To try and resolve this issue, I've dumped all the views that are composing my UITableView and then colored them to try and find the view I need to change:
As you can see, I've been unable to color that specific spot. Said differently, I can't find a view associated with that spot.
How do I remove that shading from my cells?
The table view is restarting the gradient behind each section, since it is just taking the colour you have given it.
Instead, use the backgroundView property of the table view - create an image view using your image, and set this as the background view, or create a UIView with background colour as you are doing above.
You will need to remove any code you have setting background colours elsewhere, and make sure your section header views either have clear backgrounds, or a contrasting background.
As a simple demonstration, I have created a grouped table view with its background colour set to clear, and the following code in viewDidLoad of the table view controller:
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *backgroundView = [[UIView alloc] initWithFrame:self.tableView.bounds];
CAGradientLayer *layer = [CAGradientLayer layer];
layer.colors = [NSArray arrayWithObjects:(id)[UIColor redColor].CGColor,(id)[UIColor whiteColor].CGColor, nil];
layer.frame = backgroundView.frame;
[backgroundView.layer addSublayer:layer];
self.tableView.backgroundView = backgroundView;
}
This gives the following (admittedly, pretty ugly!). The cells "float" on top of this.
I have a grid of 30 UIButtons and potentially even more, subclassed to be rendered using layers: a base CALayer, a CAShapeLayer, a CAGradientLayer and a CATextLayer. I am trying to minimize the overall time required to render/display the buttons when the corresponding xib file is loaded. If I simply setup each button in turn in viewDidLoad, the time required for the view to appear is about 5-6 seconds, which is clearly too much.
In order to speed-up the buttons setup, I am using Grand Central Dispatch as follows. In viewDidLoad, I setup each button layers using dispatch_async on the global queue (adding to the base layer the shape and the gradient layers), so that the buttons can be rendered in a different thread. A the end of the block, the CATextLayer is added to the gradient layer.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
CGRect base_bounds = CGRectMake(0, 0, self.layer.bounds.size.width, self.layer.bounds.size.height - self.layer.bounds.size.height * 0.10f);
CGPoint move_point = CGPointMake(0.0f, base_bounds.size.height * 0.10f);
self.layer.masksToBounds = NO;
baseLayer = [CALayer layer];
baseLayer.cornerRadius = 10.0;
baseLayer.shadowOffset = CGSizeMake(0.0f, 2.0f);
baseLayer.shadowOpacity = 1.5f;
baseLayer.shadowColor = [UIColor blackColor].CGColor;
baseLayer.shadowRadius = 2.5f;
baseLayer.anchorPoint = CGPointMake(0.5f, 0.5f);
baseLayer.position = move_point;
CAShapeLayer *shape = [CALayer layer];
shape.bounds = base_bounds;
shape.cornerRadius = 10.0;
shape.anchorPoint = CGPointMake(0.0f, 0.0f);
shape.position = move_point;
shape.backgroundColor = [UIColor darkGrayColor].CGColor;
gradient = [CAGradientLayer layer];
gradient.anchorPoint = CGPointMake(0.0f, 0.0f);
gradient.position = CGPointMake(0.0f, 0.0f);
gradient.bounds = base_bounds;
gradient.cornerRadius = 10.0;
gradient.borderColor = [UIColor colorWithRed:0.72f
green:0.72f
blue:0.72f
alpha:1.0].CGColor;
gradient.borderWidth = 0.73;
gradient.colors = [NSArray arrayWithObjects:
(id)[UIColor whiteColor].CGColor,
(id)[UIColor whiteColor].CGColor,
nil];
[baseLayer addSublayer:shape];
[baseLayer addSublayer:gradient];
[self.layer addSublayer:baseLayer];
[textLayer setBounds:gradient.bounds];
[textLayer setPosition:CGPointMake(CGRectGetMidX(textLayer.bounds), CGRectGetMaxY(textLayer.bounds) - 6)];
[textLayer setString:self.titleLabel.text];
[textLayer setForegroundColor:[UIColor blackColor].CGColor];
[gradient addSublayer:textLayer];
});
This approach reduce the overall time to about 2-3 seconds. I am wondering if anyone can suggest a faster way to render the buttons. Please note that I am not interested to any solution which discards the use of layers.
Thank you in advance.
Perhaps I'm missing the point but wouldn't you be better off overriding the UIButton drawRect: method and doing your drawing in CoreGraphics (CG) things would be drawn A LOT faster than seconds, you can easily do gradients, text, images with the CG API. If I understand correctly you have 4 layers per button and 30+ buttons in the same view (120+ layers)? If so, I don't think you are meant to draw so many layers (rendering/blending all of them individually would explain the huge render time). Another possibility would be to have 4 big layers, for all of the buttons.
Here's my idea,
Separate your CALayers from UIButton - UIKit won't allow anything on the background thread.
When you have an opportunity in a screen preceding the button grid, use [buttonGridViewController performSelectorInBackground:renderCALayers] to render your CALayers in the background.
When you load your button grid in viewDidLoad, overlay UIButtons with type UIButtonTypeCustom, and backgroundColor = [UIColor clearColor] over the top of your 'button' CALayers (make sure to call bringSubviewToFront on the UIButtons so they get the touch events).
If you don't know how many buttons you are rendering, you might want to pre-render the maximum number you might display.
If you have any questions please comment.
EDIT:
A few related items,
What to replace a UIButton to improve frame rate?
UI design - Best way to deal with many UIButtons
How to get touch event on a CALayer?
I believe the only way you'll get quicker load from this point is to either replace or remove UIButtons altogether and intercept touch events with another approach. That way all rendering can be done in a background thread before the view is presented.
Have you considered using NSOperation to generate your button's layers? In your code, you'd instantiate an operation queue and then iterate through each button, firing an operation (passing the respective parameters required to generate that button's layer) into the queue as each button is initiated.
Within your NSOperation, generate your layer using CoreGraphics, then pass the layer back to the button that initiated the operation (in this case, the destination button) via your completion handler (be sure to call that on the main thread). This should give you pretty good performance as your buttons layer's will be rendered off the main thread using CoreGraphics, then passed back to UIKit for presentation only after the layer has been rendered.
NSOperation sits atop GCD, has a marginal overhead but provides benefits in your case, such as the ability to set dependent prerequisites and prioritization.
Also, I'm pretty sure you shouldn't call UIColor in the GCD block you shared.
Hope this is helpful!
I am trying to add round corner to UISearchBar at the top-left and top-right corners.
However, I found the mask layer doesn't work on UISearchBar, no matter how I set it.
Here is my code
// UIView* bar = [searchBar_.subviews objectAtIndex:0]; // try to add mask to background view but also failed
UIView* bar = searchBar_;
CGRect toolbarBounds = bar.bounds;
CAShapeLayer *maskLayer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect: toolbarBounds
byRoundingCorners:UIRectCornerTopLeft
cornerRadii:CGSizeMake(5.0f, 5.0f)];
[maskLayer setPath:[path CGPath]];
[maskLayer setFillColor:[[UIColor greenColor] CGColor]];
bar.layer.masksToBounds = YES;
bar.layer.mask = maskLayer;
I have also tried the "Clip subviews", but it doesn't work either.
Then I try something more, I just set the background view (subview 0) hidden. To mu surprise, it is still visible.
Is there any magic in the UISearchBar?
The mask does work on UIToolbar.
Your problem could be related to how the mask property is implemented. It will not work if you add a mask layer to a layer which is already part of a hierarchy. The docs for CALayer say:
When setting the mask to a new layer, the new layer’s superlayer must first be set to nil, otherwise the behavior is undefined.
You'll have to remove the search bar from its superview before adding the mask layer. Wrap your last line like so:
UIView *superview = [bar superview];
[bar removeFromSuperview];
bar.layer.mask = maskLayer;
[superview addSubview:bar];
You can also use removeFromSuperlayer and addSublayer: if you prefer. Also, if your search bar isn't the topmost view in your hierarchy, make sure to find out where it is and put it back in the right place using insertSubview:atIndex: or one of the similar methods.
I need to add a bevel to my UITableViewCell's (white line on top, dark line on bottom). I already have a CAGradientLayer added as a subview of my cell.layer. Unfortunately I can't use a UIImageView as a background for my cells, so this will need to be done in code. Any suggestions? Thanks!
This is the code I have now for my cell background.
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = cell.frame;
gradientLayer.colors = [NSArray arrayWithObjects:
(id)[[UIColor colorWithRed:0.988 green:0.988 blue:0.988 alpha:1.0] CGColor],
(id)[[UIColor colorWithRed:0.9294 green:0.9294 blue:0.949 alpha:1.0] CGColor],
nil];
[cell.layer insertSublayer:gradientLayer atIndex:0];
This looks fine, but I would like to have a 1 pixel dark line on the bottom and a 1 pixel white line on the top to finish the look.
You can, and probably should use a UIImageView as the cell background. What you are currently doing is actually quite wrong.
A UITableViewCell consist of quite a few views in a hiarchy, it is important to know where you are supposed to hook in your views. This hierarchy is as follows:
UITableViewCell - Never touch this
backgroundView - Replace set your custom background for all rows.
selectedBackgroundView - Replace to set a custom background for selected/highlighted rows.
contentView - Do not set this but feel free do add as many subviews as you like.
titleLabel
detailTitleLabel
imageView
Your own views
accessoryView
It is not obvious from the start how to replace the backgroundView and the selectedBackgroundView. The UITableView itself will set these automatically after you have returned the cell from the -[tableView:cellForRowWithIndexPath:] datasource method. Meaning anything you set there will always be overridden.
The trick is to implement the -[tableView:willDisplayCell:forRowAtIndexPath:] delegate method, and set your custom background here.
All this is quite well explained in the Table View Programming Guide.
Have a look at this link. You can ignore the gloss effect, but otherwise I think it's what you want.