NSMenuItem image is distorted when drawn - objective-c

I'm building up a list of submenu items, that contain an image. I do load an image from the bundle, then want to draw it in the menu item swatch.
Here's the code:
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:#"Title" action:nil keyEquivalent:#""];
[item setAction:#selector(action:)];
[item setTarget:self];
[item setEnabled:YES];
NSImage *img = [[NSImage alloc] initWithSize:NSMakeSize(24, 16)];
[img lockFocus];
NSString *imgFile = [mainBundle pathForResource:#"swatch" ofType: #"png"];
NSImage *swtch = [[NSImage alloc] initWithContentsOfFile:imgFile];
NSRect imgRect;
imgRect.origin = NSZeroPoint;
imgRect.size = [swtch size];
[swtch drawInRect:NSMakeRect(0, 0, 24, 16)
fromRect:imgRect
operation:NSCompositingOperationSourceOver
fraction:1.0f];
[img unlockFocus];
[item setImage:[img copy]];
If I select the menu item from the submenu, the NSPopupButton displays the correct image.
If I get a bitmap representation from "img", it is correct, a downsized copy of "swtch".
But the menu item looks wrong:
It only started this on Big Sur, prevoiusly all looked good.
Do I need to setup menu item or its cell different?

It looks like the problem is in the image itself. It probably has an alpha channel, but the antialiasing around the text edges was composited on a white background (hard to explain, but it can and does happen depending on how the image was made.)
Does this image always draw on a white background? If so, try re-saving the .png file without transparency. You can do this in Preview.app. File->Export, uncheck Alpha.
If you need black antialiased text that composites properly atop any colored background, you'd need to redo the image so there's no white in it. This can be done in Photoshop.
(You might also be able to fix it by changing the blend mode to something like NSCompositingOperationDarken or NSCompositingOperationPlusDarker, but it would be cleaner to repair the image file.)

Related

Why is [UIImage imageNamed] displaying a blue rectangle instead of my image?

I'm trying to use an image for a button in the Nav Bar. It appears to be finding the image, because when I run the app, there is blue rectangle roughly the size of my image centered on where the nav bar button is supposed to be. And when I click it, the action specified for the button happens. But why is the image not displayed? Here's my code:
UIImage *image = [UIImage imageNamed:#"newTweetSmall"];
[self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:image style:UIBarButtonItemStyleBordered target:self action:#selector(newTweet)];
I've also tried
UIImage *image = [UIImage imageWithContentsOfFile:#"newTweetSmall"];
But that appears to not be finding the image at all, because there is no big blue rectangle, no button in the Nav Bar at all.
Here's the structure of my project, in case that matters. I've also tried using 'Resources/newTweetSmall' for the path of the image, but that results in no image or button either.
Your code is correct. Try use a png image with alpha channel.
It certainly solve your problem.
If you want to be convinced that your file was loaded to memory check the value in the image variable. If image is loaded this variable cannot be nil.

How to properly position the back button in iOS7

I used this code to use a custom image as the back button in the whole app.
[[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:#"back"]];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:#"back"]];
The image dimensions are 30 x 30.
The code adds the image as the back button but the position is not the correct, as you can see in the following image:
Any ideas on how to properly position the image without modifying its dimensions (at least the visual part of the image (circle + arrow))?
EDIT:
I don't want to use a custom back button because that forces me to disable the swipe/back-gesture in iOS7
EDIT
I think I might have found the trick (in iOS 7 Design Resource -- UIKit User Interface Catalog.)
Under Bar Button Items
Note that a bar button image will be automatically rendered as a template image within a navigation bar, unless you explicitly set its rendering mode to UIImageRenderingModeAlwaysOriginal. For more information, see Template Images.
Under Template Images they have some code to specify the UIImageRenderingMode.
UIImage *myImage = [UIImage imageNamed:#"back"];
UIImage *backButtonImage = [myImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
// now use the new backButtomImage
[[UINavigationBar appearance] setBackIndicatorImage:backButtonImage];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:backButtonImage];
Try creating the UIImage with alignment insets and then set the Back Indicator image.
UIEdgeInsets insets = UIEdgeInsetsMake(10, 0, 0, 0); // or (0, 0, -10.0, 0)
UIImage *alignedImage = [[UIImage imageNamed:#"back"] imageWithAlignmentRectInsets:insets];
[[UINavigationBar appearance] setBackIndicatorImage:alignedImage];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:alignedImage];
You might also try adjusting the position of the UINavigationBar title text
[[UINavigationBar appearance] setTitleVerticalPositionAdjustment:(CGFloat)adjustment forBarMetrics:(UIBarMetrics)barMetrics];
Well just follow one of the suggestions to fix the layout and lose the iOS 7 "back gesture", and then fix it with a UIScreenEdgePanGestureRecognizer!
A UIScreenEdgePanGestureRecognizer looks for panning (dragging) gestures that start near an edge of the screen. The system uses screen edge gestures in some cases to initiate view controller transitions. You can use this class to replicate the same gesture behavior for your own actions.
PLEASE SEE EDIT BELOW!!!
I created a custom back button in iOS7 not too long ago. Mine has an arrow and the word back on it. I do think pawan's suggestion is a good start. To create the back button with your custom image you can use,
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:self action:#selector(backButtonClicked)];
[backButton setBackgroundImage:finalImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[backButton setTitlePositionAdjustment:UIOffsetMake(-20, 0) forBarMetrics:UIBarMetricsDefault];
self.navigationItem.leftBarButtonItem = backButton;
My image finalImage is a composite of two different images, but you can just use your "back" image. But I think that is where the problem lies. My image was a composite, you might want to make a composite as well, but put a clear space above your back icon. I placed a clear space to the right of my icon to adjust it's spacing. Here is the code,
UIImage *arrow = [UIImage imageNamed:#"back.png"];
UIImage *wordSpace = [UIImage imageNamed:#"whiteSpace.png"];
CGSize size = CGSizeMake(arrow.size.width + wordSpace.size.width, arrow.size.height);
UIGraphicsBeginImageContext(size);
[arrow drawInRect:CGRectMake(0, 0, arrow.size.width, size.height)];
[wordSpace drawInRect:CGRectMake(arrow.size.width, 0, wordSpace.size.width, wordSpace.size.height)];
UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The Image wordSpace is a clear png that I made in photoshop so my new back button image was not stretched. You might want to place a clear png on top, to push the icon down a little. Make the size.height of it in photoshop for what you think the adjustment should be. You might need to futz with this a bit. And make sure to change up the CGSize so that it fits your icon and the clear space.
My word back was a bit off, so I looked at
[backButton setTitlePositionAdjustment:UIOffsetMake(-20, 0) forBarMetrics:UIBarMetricsDefault];
I had to play around with that line a bit to make it look as good as possible but it finally gave me what I wanted with the -20. I even adjusted the second variable which is 0 in mine, this moved the actual icon around. -5 put the icon down way to far, but its another option from the clear png.
Now to deal with the fact that you want it to be an actual back button. Look at the first line of code I posted. The action on the button is #selector(backButtonClicked). So all you need to do is make that method and you should be good to go!
- (void)backButtonClicked
{
NSLog(#"going back");
[self.navigationController popViewControllerAnimated:YES];
}
Hope this helps a bit.
EDIT*****
I was playing around with my code a little bit and found a better way to move the back icon. I just used a ship's wheel because I didn't have the same one that you did, but it will work the same.
Since you don't really want a title you can create the button with this code,
UIImage *image = [UIImage imageNamed:#"781-ships-wheel.png"];
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithImage:image style:UIBarButtonItemStylePlain target:self action:#selector(backButtonClicked)];
Just change the 781 stuff with your icon's name. Then you can move it around with the following,
[backButton setImageInsets:UIEdgeInsetsMake(20, 0, -20, 0)];
Take a look at this picture.
This shows the icon down considerably, but I wanted to show you the idea. The numbers for the Edge insets are Top, Left, Bottom, and Right. Don't touch the left and right if you don't need to move it that way, change the top and bottom. Notice however, that if you need to move it down by 20 points like I did, (way too much) you need to offset in the negative for the bottom, or the icon will get compressed. This is what it looks like with all zero's.
So you can pretty much move it where ever you want, but you will still have to set up the #selector(backButtonClicked) to make it work like the real back button.
This is Swift 2 version.
The simplest way is like this. Put this code in AppDelegate.'
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let navigationBarAppearace = UINavigationBar.appearance()
let image = UIImage(named: "back-btn")
navigationBarAppearace.backIndicatorImage = image
navigationBarAppearace.backIndicatorTransitionMaskImage = image
return true
}
if your back button has background colour, it may won't work correctly.
Add your icon to asset folder for each resolution like this:
You can try this
self.navigationItem.leftBarButtonItem.imageInsets = UIEdgeInsetsMake(0, 0, 10, 0);
The problem is that your image is too tall. To prove this, first try this code:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(10,20), NO, 0);
CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(6,0,4,20));
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.navbar.backIndicatorImage = im;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(10,20), NO, 0);
UIImage* im2 = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.navbar.backIndicatorTransitionMaskImage = im2;
It looks fine. Now change the 20 to 30 in the two CGSizeMake calls:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(10,30), NO, 0);
CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(6,0,4,20));
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.navbar.backIndicatorImage = im;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(10,30), NO, 0);
UIImage* im2 = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.navbar.backIndicatorTransitionMaskImage = im2;
The icon is now too high.
So just make your image 20 pixels tall and all will be well.
UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, -2, 0); // or (2,0,0,0)
UIImage *backArrowImage = [[UIImage imageNamed:#"back"] imageWithAlignmentRectInsets:insets];
[[UINavigationBar appearance] setBackIndicatorImage:backArrowImage];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:backArrowImage];

UITabBar unselected icon tint

I am trying to change the color of my tab bar icons when the tabs are UNselected. Right now the color is default grey and I can change the color to whatever color I want for when it IS selected.
Apple's dev library said to change the image rendering to "original" instead of its default mode "template." I did that. then it says to use initWithTitle:image:selectedImage: I tried to do that as well but I think that's where I messed up. I wrote this in my viewcontroller.m file. What's wrong here?
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIImage *stat = [UIImage imageNamed:#"white_stats.png"];
stat = [stat imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
(instancetype)initWithTitle:(NSString *)nil image:(UIImage *)stat selectedImage:(UIImage *)stat;
}
The problem is that you are using the same UIImage with UIImageRenderingModeAlwaysOriginal in both places.
Your code should look something like
UIImage *stat = [UIImage imageNamed:#"white_stats.png"];
UIImage *statAlwaysOriginal = [stat imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
self.tabBarItem = [[UITabBarItem alloc] initWithTitle:nil image:statAlwaysOriginal selectedImage:stat];
The other thing is that there are some actual syntax errors in your post (in the UITabBarItem initialization, but I suspect you just pasted it incorrectly.

iOS - Rotating an image or picture

My iOS app downloads some images from the internet and displays them on the screen (iPhone Portrait layout). Some of these images are more wider than taller, and in that case, when the image is presented to the screen, they appear squished (imagine the picture of a widescreen tv shrunk to iPhone's width). What I want to do is that everytime the width of the image is wider than the height of the image, I want to rotate the picture by 90 degrees clockwise (into landscape layout mode), save it on app's documents folder, and then present it on the screen - this way, the picture of the widescreen tv (e.g.) appears 90 degrees rotated but the image aspect ratio is not totally destroyed.
For various complicated reasons, I can't use landscape layout of my app - too many other side effects. So this is code I wrote:
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
CGFloat width = image.size.width;
CGFloat height = image.size.height;
if(width > 1.2*height) {
NSLog(#"rotate the image");
CGImageRef imageRef = [image CGImage];
image = [UIImage imageWithCGImage:imageRef scale:1.0 orientation:UIImageOrientationLeft];
}
Then I save the image into App's documents folder. Then a new UIViewController opens which reads the image file saved in the documents folder and then opens this image. Problem is, the image doesn't appear rotated at all - just appears the same way as the original - without any rotation. I do know that the above code tries to do what it is supposed to do because I do see NSLog "rotate the image" in the console. But somehow this image doesn't get saved as the rotated image.
So, how should I approach this issue?
EDIT:
Code to save my image:
NSString *documentsDirectory = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
// Create image name
NSString *path = [#"" stringByAppendingFormat:#"%#%#", #"image", #".png"];
// Create full image path
path = [documentsDirectory stringByAppendingPathComponent:path];
path = [NSString stringWithFormat:#"%#", path];
// Write image to image path
NSData *data1 = [NSData dataWithData:UIImagePNGRepresentation(image)];
[data1 writeToFile:path atomically:YES];
The following website's solution ultimately worked for me:
http://www.catamount.com/blog/uiimage-extensions-for-cutting-scaling-and-rotating-uiimages/

Retina display for an image from URL

I have some images I need to get from the web. Just using data from a URL.
They need to show correctly on Retina Display.
When I get the images from the web, they still look pixelated. I need to set the images' scale to retina display (2.0), but I must be missing something.
Here's what I did so far.
UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:#"http://www.msdomains.com/tmp/test.png"];
CGRect labelFrame = CGRectMake(0,0,64,64);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:labelFrame];
imageView.contentScaleFactor = [UIScreen mainScreen].scale;
[imageView setImage:img];
[self addSubview:imageView];
[imageView release];
Try adding ##2x.png at the end of your URL. That wont change the URL, but the image will be recognized as a retina #2x image. It worked for me, but I used this method with SDWebImage.
e.g. using http://www.msdomains.com/tmp/test.png##2x.png.
Your code should work pretty much as-is. I don't know what the original dimensions of your image were, but I'd guess they were 64x64 px. In order to scale down correctly, the original image would need to be 128x128 px.
As a test, the following code correctly displayed my photo in Retina resolution on the Simulator, and on my iPhone 4:
UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://www.seenobjects.org/images/mediumlarge/2006-08-19-native-lilac.jpg"]]];
CGRect labelFrame = CGRectMake(0, 0, 375, 249.5);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:labelFrame];
[imageView setImage:img];
[self.view addSubview:imageView];
Note that the UIImageView is 375x249.5 points, which is half of the original (pixel) dimensions of the photo. Also, setting the contentScaleFactor didn't seem to be necessary.
(As an aside, I can't see that specifying #2x on the URL will help, in this case, as the call to dataWithContentsOfURL: will return an opaque blob of data, with no trace of the filename left. It's that opaque data that's then passed to imageWithData: to load the image.)
when you directly assign the image URL to imageView, it will not take it as retina.
imageView.imageURL = [NSURL URLWithString:#"http://example.com/image.png"];
will not give you a retina image.
So, inspite your image is 200x200 but if your imageView is 100x100 then it will take 100x100 from the downloaded image and show pixelated image on retina devices.
Solution would be to use the image property of imageView instead of imageURL.
imageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://example.com/image.png"]]];
This will assign 200x200 image to the imageView of 100x100 and hence the image will not be pixelated.
For retina display, add the same image with the resolution which is exactly the double of the original image. dont forget to add "#2x"at the end of this image name... e.g. "image_header.png" is an image 320x100 then another image with name "image_header#2x.png" (dimension 640x200) will be selected for the retina display automatically by the OS...
hope it helps