I have an array of UIImageViews. I want to apply a shadow to each of these images. I've used the code below:
- (void)awakeFromNib {
for (UIImageView *image in imagesJigsawPieces) {
image.layer.shadowColor = [UIColor blackColor].CGColor;
image.layer.shadowOffset = CGSizeMake(-1, -1);
image.layer.shadowOpacity = 1;
image.layer.shadowRadius = 5.0;
image.clipsToBounds = NO; //EDIT: I have also included this with no change
}
}
I have also included #import <QuartzCore/CALayer.h>.
I am not getting any errors but I am also not getting any shadows on my images.
Are you certain this code is being called? Have you placed a breakpoint in the for loop to verify?
-awakeFromNib is called only if you have a view (or whatever) in a nib file connected via IBOutlet to an ivar in your code. -awakefFromNib is called, in this case, instead of -initWithFrame: (or the like), an important distinction which I sometimes forget myself!
Related
This is what I want to do:
As you can see i want to:
Decrease the width of the tableView (I want more margin on the sides than the grouped tableView provides)
Corner radius (bigger radius than the default for grouped tableView)
Drop shadow around the table and a special shadow beneath the last cell
You can do this by "drawing" the backgroundView of the cells yourself.
I'd recommend getting an image to use as the background (if the cells are all the same height).
You'll need three images.
A "top" image with the top corners rounded.
A "bottom" image with the bottom corners rounded and the drop shadow how you want it.
And a "middle" image with no rounded corners.
If the cells don't have any texture or gradient within them then you can use stretchable images to reduce the memory footprint of them.
Then I would subclass the UITableViewCell and override the backgroundView to add a UIImageView. I'd also provide an accessor method to change the type (top, middle, bottom) of the cell.
Each cell can then have three placeHolder properties of a UIImage (topImage, bottomImage and middleImage). When the type of the cell is changed these can be accessed (use lazy instantiation to make sure they are only loaded once and only when needed) and then set the backgroundVIew image to be the required image.
Something like this...
In the UITableViewCell subclass define a type enum...
typedef enum {
CellTypeTop,
CellTypeMiddle,
CellTypeBottom
} cellType;
Then a property for the type...
#property (nonatomic) cellType cellType
Then in the .m ...
Define some more internal properties...
#property UIImageView *bgImageView;
#property UIImage *topImage;
#property UIImage *middleImage;
#property UIImage *bottomImage;
Then add the imageView (only once)...
- (void)awakeFromNib //or in the init depends how you are initialising the cell
{
self.bgImageView = [[UIImageView alloc] initWithFrame:blah];
[self.backgroundView addSubView:self.bgImageView];
}
Now when the type is changed...
- (void)setCellType:(cellType)cellType
{
switch(cellType) {
case CellTypeTop:
self.bgImageView.image = self.topImage;
break;
case CellTypeMiddle:
self.bgImageView.image = self.middleImage;
break;
case CellTypeBottom:
self.bgImageView.image = self.bottomImage;
break;
}
}
Finally a lazy instantiation of the images...
- (UIImage *)topImage
{
if (_topImage == nil) {
_topImage = [UIImage imageNamed:#"topImage"];
//alternatively...
_topImage = [[UIImage imageNamed:#"topImage"] stretchableImageWith...
}
return _topImage;
}
Now repeat these for the other images.
This will be more performant (by a long way) than using a CALayer alternative and, especially if using the stretchable images, will have a very small memory footprint.
Several other users have said that this is not good for performance, memory, design, whatever, but it really is the best way to get the best performance for UserExperience than CALayers. Yes, it will use more memory than CALayers but only marginally and it will get to a limit as there are only a few dequeueable cells created.
A couple of links explaining performance issues when using CALayers in scrollViews...
http://www.quora.com/iOS-Development/What-is-the-best-way-to-optimize-the-performance-of-a-non-paging-but-view-recycling-UIScrollView-involving-loading-potentially-caching-and-displaying-bundled-images
Bad performance on scroll view loaded with 3 view controllers, drawn with CALayer
::EDIT:: Edit to answer Michael's question.
In the storyboard create a UITableViewController (rename the Class in the inspector so that it matches your subclass UITableViewController - I'll call it MyTableViewController).
Create a subclass of UITableViewCell (I'll call mine MyTableViewCell) in the code (i.e. the .h and .m).
Add the above code to do with properties and types and imageViews to your MyTableViewCell.h file.
In the storyboard select the cell in the TableViewController and rename the class to MyTableViewCell. Also set the reuse identifier on it.
In the MyTableViewController code you will need a function like this...
-(UITableViewCell*)tableView:(UITabelView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
MyTableViewCell *cell = [tableView dequeueCellWithReuseIdentifier:#"Cell"];
cell.cellType = CellTypeTop; //or whichever it needs to be
cell.textLabel.text = #"Blah";
return cell;
}
Oh, another thing, in the storyboard you will be able to layout your cell how you want it to look and link up all the labels and imageviews etc... Make sure you add IBOutlet to the UIImageView so that you can link it up in the storyboard.
make sure you have #import <QuartzCore/QuartzCore.h> imported, then you can start accessing the layers of the UITableView like.
UITableView *yourTable = [[UITableView alloc] initWithStyle:UITableViewStyleGrouped];
[[yourTable layer] setCornerRadius:10.0f];
[[yourTable layer] setShadowColor:[[UIColor blackColor] CGColor]];
[[yourTable layer] setShadowOffset:CGSizeMake([CALayer ShadowOffSetWidthWithFloat:10.0f], [CALayer ShadowOffSetWidthWithFloat:10.0f])];
[[yourTable layer] setShadowOpacity:[CALayer ShadowOpacity:1]];
[[yourTable layer] setMasksToBounds:NO];
UIBezierPath *path = [UIBezierPAth bezierPathWithRect:yourTable.bounds];
[[yourTable layer] setShadowPath:[path CGPath]];
This will add shadow affect to your table view with the shadow not masked to the bounds of the UITableView, at setCornerRadius you can set the corners of the table to whatever you want. You can also set the frame of the UITableView by doing
[yourTable setFrame:CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)];
EDIT
As another user has tried to point out that CALayer is very slow, this is not the case
CALayer was introduced to help performance issues around animation. Please read documentation. Loading an image straight in may seem like a good idea but in the long run will take up more memory. Please this question about memory allocation for images. As you can see it may seem faster, but it takes up 2.25 MByte of memory per image which after loading each image so many times your app will start to become slow.
I have in my controller two UIView members, progressLineView and buttonsView. At some point I call this method:
- (void) drawPlayProgressLine{
progressLineView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 1, buttonsView.frame.size.height)];
progressLineView.backgroundColor = [UIColor whiteColor];
[buttonsView addSubview:progressLineView];
}
Everything works fine, and I also have a method that changes the position of the view:
- (void) moveProgressLine{
CGRect frame = progressLineView.frame;
frame.origin.x++;
progressLineView.frame = frame;
}
After the moveProgressLine method is called a few times and I want to call drawPlayProgressLine again, instead of completely moving the view to the starting position, it creates a new view. The more drawPlayProgressLine is called, the more views I get on my screen but I only need one.
I don't understand how this can happen when I'm creating only one object. How can I move the view instead of having a new one created each time? And another question: how can completely remove it (until the drawPlayProgressLine method is called to create it again)
I don't understand how this can happen when I'm creating only one object.
You create a new view every time you call your -drawPlayProgressLine method. Call it 10 times, you get 10 views.
How can I move the view instead of having a new one created each time?
Don't create the view each time through -drawPlayProgressLine. Instead, you can do either of:
Create progressLineView once, when the view controller's view hierarchy is created. -viewDidLoad is a perfect place for that sort of thing.
Check the value of progressLineView and create it only if it is currently nil.
Whichever you choose, assuming progressLineView is an instance variable, you can do exactly what you're doing in your -moveProgressLine method. That is, just use progressLineView as though it already exists, because it does. BTW, an easy way to move a view is to modify it's center property:
CGPoint *c = progressLineView.center;
c.x += 25.0;
progressLineView.center = c;
And another question: how can completely remove it (until the
drawPlayProgressLine method is called to create it again)
One approach is to simply hide the view when you're not using it. Another is to remove it from its super view (and release it if you've retained it), and then set your progressLineView to nil. So, if progressLineView is an ivar, do this:
[progressLineView removeFromSuperview];
[progressLineView release]; // if you're not using ARC and have retained it
progressLineView = nil;
you should just check if its created yet before creating it and move it if necessary:
- (void) drawPlayProgressLine{
if(progressLineView == nil)
{
progressLineView = [[UIView alloc] init];
progressLineView.backgroundColor = [UIColor whiteColor];
[buttonsView addSubview:progressLineView];
}
progressLineView.frame = CGRectMake(0.0, 0.0, 1, buttonsView.frame.size.height);
}
You are probably not invalidating the parent view. New objects are not created, but rather their presentation is left on the screen after you move them.
As for the second question:
[progressLineView removeFromSuperview];
[progressLineView release];
progressLineView = nil;
I have some code that sets a border around a UITextView. It builds correctly in one class; when I take that code and copy it to another class (changing the object name), it no longer builds, saying "Property 'borderWidth' cannot be found in forward class object 'CALayer *'" (the same message for the other two lines of code). I have done a clean, re-build and nothing helps. Why is this happening? and how do I fix it?
- (void)viewDidLoad {
[super viewDidLoad];
//-- draw box around notes field
orderNotes.layer.borderWidth = 1.0f;
orderNotes.layer.borderColor = [[UIColor blackColor] CGColor];
orderNotes.layer.cornerRadius = 4;
}
Object "orderNotes" is defined as UITextView. There are no other errors.
You need to:
#import <QuartzCore/QuartzCore.h>
Otherwise, the layer property is not visible to you.
I'm trying to change background color of one of my UIView subclasses. For some reason self.backgroundColor = [UIColor whiteColor];doesn't do anything when I put it in my - (id)initWithFrame:(CGRect)framemethod inside the view. The view is always black. I have also tried self.myView.backgroundColor ... from my view's controller, but that didn't work either. Any ideas on what I'm doing wrong?
The relevant code looks like this:
[...]
#interface PaperView : UIView
[....]
[...]
#implementation PaperView
[...]
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[...]
// Initialization code
self.backgroundColor = [UIColor whiteColor]; // This doesn't do anything, the view is always black.
}
return self;
}
If this view is being unarchived from a xib, you need to override -initWithCoder:. -initWithFrame: is only invoked if you are creating your view programmatically.
I had this same problem. I hooked into layoutSubviews() and it worked ok:
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundColor = UIColor.clearColor()
}
This is indicative of the view not having a frame set to it. I recommend setting a breakpoint in your initWithFrame: to verify that its being called. If you were to call, say, ... = [UIView alloc] init], then that could be the source of your problem.
EDIT
If initWithFrame: is in fact being called, it's possible that the view is being covered by another view giving the appearance that it's not working (since you don't see it) or that the view itself is hidden.
Another way to troubleshoot is to override the backgroundColor property and set a breakpoint. Find out what else, in the callstack, is changing the color.
You may use self.layer.backgroundColor instead:
mySubclassedView.layer.backgroundColor = UIColor.green.cgColor
In what method do you call self.myView.backgroundColor?
Are you sure, that it's after viewDidLoad: ?
But, have no idea what is wrong with your first method.
Could you show more code?
I just had this exact same problem. The background color did not show even though I set the correct frame and set the background color to white in my custom init method, as well as in my viewWillAppear method. I also verified that nothing was covering it.
Then I found the solution: I set the background color in viewDidAppear instead, and all was fine:
- (void)viewDidAppear:(BOOL)animated
{
[self.view setBackgroundColor:[UIColor whiteColor]];
self.view.frame = _viewFrame;
}
(The _viewFrame CGRect was passed in to my init method.)
Another option is to set it in - (void)viewDidLayoutSubviews, depending on when and how exactly you want to set your background color.
To be entirely honest, I don't understand (yet) why setting the background color in viewDidAppear worked while it didn't work in the init method, and the code for setting it was identical in both places.
Hope this helps,
Erik
UPDATE:
It does have something to do with view's frame. When I set my view's frame in my init method, then setting the background color in the viewDidAppear no longer has the desired effect. This is even the case if I set my view's frame after my view build method that creates the sub views. So the real mystery is: between the point where I am done creating my view and the point where it is displayed, what in the view's life cycle is causing the view's frame to be reset to something that is incorrect?
So, the answer really is: it will work as long as your frame is set correctly and your view is visible. Just check your view's frame throughout the view's lifecycle to make sure it's correct.
Tricky...
Why can't you implement self.backgroundColor = [UIColor whiteColor] in -viewDidLoad method instead of -initWithFrame? Then try self.backgroundColor = [UIColor whiteColor]; as well as self.myView.backgroundColor to see which works.
In my application, when the user clicks an infoButton, it should add another view to the screen at a specific location. I'm trying to achieve this behavior with the following method:
- (IBAction)showInfo1:(id)sender
{
UIView *myView1 = [[UIView alloc] initWithFrame:CGRectMake(25,25,50,20)];
[self.view addSubview:myView1];
}
(I declared everything in the header file of my class.)
When I run the code and press the button, nothing appears to happen (I don't see the new view).
I also noticed that XCode is displaying the following warning:
Local declaration of 'myView1' hides instance variable.
Does anyone have any ideas?
How do you know nothing changes?
It looks like myView1 doesn't actually contain anything. Try setting the background color of myView1
- (IBAction) showInfo1: (id) sender
{
UIView *myView1 = [[UIView alloc] initWithFrame:CGRectMake(25,25,50,20)];
myView1.backgroundColor = [UIColor redColor];
[self.view addSubview: myView1];
}
To open it at a specific point you need to change the parameters in CGRectMake() for example
to open it at the very top left with a width of 50 and height of 20 you would do:
CGRectMake(0, 0, 50, 20)
"Local declaration of 'myView1' hides instance variable." message appears because you have declared in your class some property with the same name (even if the type is different).
If you put UIView *myView1 in your class definition, your method would look like
- (IBAction)showInfo1:(id)sender{
myView1 = [[UIView alloc] initWithFrame:CGRectMake(25,25,50,20)];
[self.view addSubview:myView1];
}
Here comes more considerations: you must to release myView1 when you stop using it, avoid to be placed more than once, etc. but here we have the basic idea.
Finally, maybe your view is already added, but 'cause it doesn't contains anything yet, you don't notice it. Also you would like to check UIViewController to see if works better for you.