UIImage resizableImageWithCapInsets: not working as expected - objective-c

I'm writing my first iOS app targeting the iOS 5.0+ platform. I'm using the UIAppearance protocol to customize the applications UI.
I'm trying to change the backgrounds for UIBarButtonItem across the entire application. Due to the fact that my UIBarButtonItem's may change size depending on the text or icon used I'm trying to utilize UIImage resizableImageWithCapInsets: with my background png.
I originally found the code I needed on Ray Wenderlich. Using the same exact code, with an image that's pretty close to the one used in the aforementioned tutorial, I'm getting weird results. Maybe it's just my inexperience with Cocoa Touch.
Here is the code I'm using.
DreamsAppDelegate.m - customizeAppearance:
UIImage *btnBg = [[UIImage imageNamed:#"navBarButton-bg"]
resizableImageWithCapInsets:UIEdgeInsetsMake(0, 6, 0, 6)];
[[UIBarButtonItem appearance] setBackgroundImage:btnBg
forState:UIControlStateNormal
barMetrics:UIBarMetricsDefault];
Here is the png background image I'm trying to use
And here is the result (in the simulator)

The section of your image between the left and right, and between the top and bottom, is tiled to fill the space needed by the image. Try UIEdgeInsetsMake(15, 6, 15, 6).
To generalize, if your image has a continuous gradient, the repeated section should only be 1 pixel high. Since your button image is 31 pixels high, the top and bottom (the first and third arguments to UIEdgeInsetsMake) should add to 30. They don't have to be equal; UIEdgeInsetsMake(8, 6, 22, 6) will move the repeated section up, resulting in a paler background.
Also, is the file you've attached the plain or the retina ('#2x') version of the image? The insets have to be within the size of the plain version.

I have the exact same problem.
Another thing you can do is to set the resizingMode of UIImageResizingModeStretch. It solved my problem.
Hopwever, it's not available in IOS5. So my solution would be to do as everyone else said. Realize that the tileable side should be just one pixel. Most buttons do not change much in the middle anyway.
So, use this code:
-(UIImage *) resizableImageWithCapInsets2: (UIEdgeInsets) inset
{
if ([self respondsToSelector:#selector(resizableImageWithCapInsets:resizingMode:)])
{
return [self resizableImageWithCapInsets:inset resizingMode:UIImageResizingModeStretch];
}
else
{
float left = (self.size.width-2)/2;//The middle points rarely vary anyway
float top = (self.size.height-2)/2;
return [self stretchableImageWithLeftCapWidth:left topCapHeight:top];
}
}

Related

Take a screenshot of an UIView where its subviews are camera sessions

I'm building an app where I need to take a screenshot of a view whose subviews are camera sessions (AVFoundation sessions). I've tried this code:
CGRect rect = [self.containerView bounds];
UIGraphicsBeginImageContextWithOptions(rect.size,YES,0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.containerView.layer renderInContext:context];
UIImage *capturedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Which effectively gets me an UIImage with the views, only that the camera sessions are black:
I've tried the private method UIGetScreenImage() and works perfectly, but as Apple doesn't allows this, I can't use it. I've also tried the one in Apple's docs but it's the same. I've tracked the problem to AVFoundation sessions using layers. How can I achieve this? The app has a container view with two views which are stopped camera sessions.
If using iOS 7, it's fairly simple and you could do something like this from a UIViewController:
UIView *snapshotView = [self.view snapshotViewAfterScreenUpdates:YES];
You can also use this link from a widow: iOS: what's the fastest, most performant way to make a screenshot programatically?
For iOS 6 and earlier, I could only find the following Apple Technical Q&A: [How do I take a screenshot of my app that contains both UIKit and Camera elements?]
Capture the contents of your camera view.
Draw that captured camera content yourself into the graphics context that you are rendering your UIKit elements. (Similar to what you did in your code)
I too am currently looking for a solution to this problem!
I am currently out at the moment so I can't test what I have found, but take a look at these links:
Screenshots-A Legal Way To Get Screenshots
seems like its on the right track - here is the
Example Project (and here is the initial post)
When I manage to get it to work I will definitely update this answer!

Managing retina UIButton image sizes

I am a bit confused on managing the different sized images with retina and non-retina displays.
I add a custom button in Storyboard and add some text and then add the backgroundimage, which is a vector done in Illustrator (Width: 630 / Height:130):
UIImage *img = [UIImage imageNamed:#"iPad1_orange_button.png"];
[myButton setBackgroundImage:img forState:UIControlStateNormal];
the button shows up:
...the button comes out very small.
I have another image with the #2x for retina but that comes out the same size.
My question is how to manage the sizes of the buttons in regards to image size. DO i need to set the size of the button manually?
Also, when i create a button in Illustrator with the same pixel size as the button i use in XCODE and export it as .png, add it to XCODE and drag it into Storyboard it comes out very large.
Just a quick clarification:
The storyboard dimensions are NOT in pixels.
An iPhone 4S has 640x960(x,y) # 326ppi. xCode dimensions are 320by460(x,y). Just take the numbers and convert em to get the appropriate pixel sizes.
IE: If you want a button that that is 100 wide and 100 tall in storyboard you'd to create an image 100*(640/320) px wide and 100*(960/460) px tall (I believe my math is right...).
Depending on what you're building for you'd need different images to cater to the different devices.
On another note, the term retina display doesn't designate a clear-cut standard of px by px. If I recall correctly it's a term Apple coined that basically means that a screen has enough px that the human eye (hence retina) would not be able to see the jaggies.
Similar to Intel's coining of the term Ultrabook; no REAL spec floor/ceiling just a marketing ploy.
Think of dimensions and what you would consider a pixel to be a "density independent pixel". You are always working in the original size when it comes to position and size but it is up to the operating system to handle the different resolutions for you. For now we have the original size and Retina which is X2 the density but this could change. You never need to manually change the resolution for the given device.
This is simple:
make the button frame the size of the normal res image
when you load the image with UIImage imageNamed: do it like this
UIImage *img = [UIImage imageNamed:#"iPad1_orange_button"];
*Note I removed the file extension (.png) - this instructs UIImage to select the resolution appropriate version, the regular or the #2x. iOS will show the 2x version of the image in the same frame size as the normal res image. Pretty simple.

How do I override the Points to Pixels iOS specificity and have my image drawn at the right size?

I have a 64px by 64px redSquare.png file at a 326ppi resolution. I'm drawing it at the top left corner of my View Controller's window as follows:
myImage = [UIImage imageNamed:#"redSquare.png"];
myImageView = [[UIImageView alloc] initWithImage:myImage];
[self.view addSubview:myImageView];
Given that the iPhone 4S has a screen resolution of 960x640 (326ppi) there should be enough room for 9 more squares to fit next to the first one. However there's only room for 4 more. i.e. the square is drawn larger than what it should given my measurements.
// even tried resizing UIImageView in case it was
// resizing my image to a different size, by adding
// this next line, but no success there either :
myImageView.frame = CGRectMake(0, 0, 64, 64);
I believe it has to do with the way the device is "translating" my pixels. I read about the distinction between Points Versus Pixels in Apple's documentation but it doesn't mention how one can work around this problem. I know I'm measuring in pixels. Should I be measuring in points? And how could I do that? How exactly am I to resize my image so that it can hold 9 more same-sized squares next to it (i.e. on the same horizontal..) ?
Thank you
To display an image at full resolution on a Retina display, it needs to have #2x appended to the end of its name. In practice, this means you should save the image you're currently using as redSquare#2x.png and a version of that image in 32x32 pixels as redSquare.png.
Once you have done this, there is no need to change your code. The appropriate image will be displayed depending on the device's capabilities. This will allow your app to render correctly on both Retina and non-Retina devices.

View behaviour differences between IOS 5.x and IOS 4.x

I've been trying to animate an image view to slide upwards on the screen. The views height is also increased whilst the position is moved upwards.
On my iPhone 4S (5.x) the image view behaves as expected, the view only moves upwards as its height is increased, however on my iPhone 3G (4.1), the view moves down a little bit during this animation.
Such a level of accuracy is needed as the image view is used to create a non expensive shadow effect. Its alignment is important for the effect. The image is a resizable graphic.
This is how I change the position and size of the view
CGRect oldShadow = self.shaddowView.frame;
oldShadow.size.height = oldShadow.size.height+200;
oldShadow.origin.y = oldShadow.origin.y - 200;
self.shaddowView.frame =oldShadow;
This is how the image for the view is set up as resizable:
UIImage* shadow = [[UIImage imageNamed:#"shadow.png"] stretchableImageWithLeftCapWidth:20 topCapHeight:20];
self.shaddowView.image = shadow;
Thanks.
I used the following animation, with a border around not only the starting position but also the intended final position, so that I could confirm whether any undesired shifting of the view (other than the obvious upward expansion) took place, but it worked fine on iOS 4.2.1:
[UIView animateWithDuration:2.0
animations:^{
CGRect newImageFrame = imageView.frame;
newImageFrame.origin.y -= stretchBy;
newImageFrame.size.height += stretchBy;
imageView.frame = newImageFrame;
}];
I don't have iOS 4.1 device sitting around (my old 3G test phone is running iOS 4.2.1, the latest supported iOS version for that device), so I can't speak to that, but it's fine with iOS 4.2.1.
I have to confess, though, that I find it very unlikely that when you animate the changing of a frame, that the final frame would not be precisely what you specified it to be. If you NSLog the frame when you're done, it is not the correct value? Or are you saying that it momentarily moves during the animation but then ends up in the correct location? Or that it shifts down during animation and ends up in the wrong position even when the animation is done?
I wonder if there's something else going on (e.g. is your animated view a subview of a scroll view, which itself might be shifting? or is there some code not shown here that is accidentally further adjusting the frame after the animation? etc.). Seems like a little debugging should confirm whether the frame is actually not what you intended, or whether there is some other issue going on.
I originally answered suggesting shouldRasterize option, but if you're trying to support old iPhone 3G devices, then maybe that's not good enough. Definitely stutters a little on these old phones. Anyway, this was my original answer:
I assume you're doing this because the layer shadow feature is a little CPU intensive. But I've heard (but can't speak to it) that if you use the shouldRasterize option, it's a little better:
viewThatNeedsShadow.layer.shadowColor = [UIColor blackColor].CGColor;
viewThatNeedsShadow.layer.shadowOffset = CGSizeMake(3.0, 3.0);
viewThatNeedsShadow.layer.shadowOpacity = 0.5;
viewThatNeedsShadow.layer.shouldRasterize = YES;
viewThatNeedsShadow.clipsToBounds = NO;

Images in NSButton and NSImageView Blurred

I am completely stumped here; I have a series of small images I'm tinkering with and making into buttons:
And as you can see they are all decently crisp and sharp, and retain this when I open the png files in Preview and what not.
However, when I use them in NSButtons and NSImageViews in Interface Builder, setting Scaling to None:
The images become horribly blurred. What am I doing wrong? I don't know where to start and what to try; should I go back to the icons and try to make them pixel perfect? Does it have to do with anti-aliasing or something along those lines?
EDIT:
For some reason, it seems as if the NSButtons and NSImageViews are loading the high resolution versions of the images, even though I'm on a normal display, which can be identified by a slight light blue stroke I added to them. For some reason, Quartz Debug does not identify these as high resolution images and there's no red tint. Removing references to the #2x images does fix the problem... but...
If you check out session 245 in the WWDC 2012 videos Advanced Tips and Tricks for High Resolution on OS X in the first section on NSImage you'll find out why.
NSImage doesn't have any concept of high resolution - it just uses the smallest image that has more pixels than the space it has to fill - so if your NSImageView is bigger in dimension than your 1x image it will use the 2x image as it has more pixels.
I have this problem before. It seems that if your image's DPI isn't 72, the image size will be wrong. You can get the real size use the code below.
NSImage *image = [NSImage imageNamed:#"image"];
NSBitmapImageRep *rep = [NSBitmapImageRep imageRepWithData:[image TIFFRepresentation]];
NSSize size = NSMakeSize([rep pixelsWide], [rep pixelsHigh]);
[image setSize: size];
When specifying image names in Interface Builder and [NSImage imageNamed:], make sure to use foo instead of foo.png. While iOS is smart enough to add the #2x in the later case, Mac OS X is not. It will load the non-retina image in the later case, but will add the #2x in the first case (if such an image is present).
Are you assigning the images to your Buttons in IB or in Code?
If you are doing it in code, maybe creating a copy of the image (e.g. [myImage copy]), and assigning that copy to your button may solve this.
In my case (drawing icons in custom NSOutlineView), I had to make sure that the x,y origin of the drawRect is rounded to int values:
NSMakeRect( round(NSMinX(cellFrame)-iconSize.width),
round(NSMidY(cellFrame)-(iconSize.height/2.0f)), …);
This is actually a response to the earlier post about DPI, but I was unable to reply directly to it. The code in that post gave the true pixel dimensions for me (that is, it did not indicate any trouble). However, image DPI was definitely the culprit in my case. The symptoms I was seeing were:
With my NSImageViews set to No Scaling, the images would appear squashed.
With my NSImageViews set to Axes Independently, most images would appear correctly if the dimensions of the NSImageViews were set to exactly match the dimensions of the image.
However, even in this case, some images had strange artifacts in them that were not there when viewing the same image via Preview or elsewhere (or even via Interface Builder, for that matter -- they only appeared at runtime).
The images that had trouble were at a DPI other than 72. When I re-created the images at 72 DPI, all of the above behavior disappeared.
This was a pretty confounding issue -- I hope this helps someone!
For me, I just needed to set image scaling to none:
In Interface Builder
In code
NSImageCell *image;
[image setImageScaling:NSImageScaleNone];
NSButtonCell *button;
[button setImageScaling:NSImageScaleNone];