iOS 11 UIBarButtonItem images not sizing - ios

The answer to my question was hinted at in this question, so I think the answer is to disable autolayout for my UIToolbar view.
The code that is said to work for views is
cButton.translatesAutoresizingMaskIntoConstraints = YES;
But I’m not sure if it applies to my code since UIToolbar doesn’t inherit from UIView.
I have lots of small images that I use in my games that are different sizes depending on the device and orientation. Rather than having lots of different images, and adding new ones when Apple introduces new devices, I decided to make one 160x160 image fore each and then resize it when it is used. This worked fine from iOS 4 - iOS 10 but fails in iOS 11.
The code is pretty straightforward:
// Get the image
NSString *pictFile = [[NSBundle mainBundle] pathForResource:#"Correct" ofType:#"png"];
UIImage *imageToDisplay = [UIImage imageWithContentsOfFile:pictFile];
UIImage *cImage = [UIImage imageWithCGImage:imageToDisplay.CGImage scale:[UIScreen mainScreen].scale orientation:imageToDisplay.imageOrientation];
UIButton *cButton = [UIButton buttonWithType:UIButtonTypeCustom];
[cButton setImage:cImage forState:UIControlStateNormal];
[cButton setTitle:#"c" forState:UIControlStateNormal];
//set the frame of the button to the size of the image
cButton.frame = CGRectMake(0, 0, standardButtonSize.width, standardButtonSize.height);
//create a UIBarButtonItem with the button as a custom view
c = [[UIBarButtonItem alloc] initWithCustomView:cButton];
This is what it looks like pre11. The bar button items have been resized and fit nicely in the bottom bar. Note I reduced the size of the checkmark by 50% just to make sure I was looking at the correct code and that it behaves like I expect.
Here’s what they look like in the simulator for Xcode 9.0 GM and iOS 11. Note that the top row of buttons resize correctly but the bottom row expand to fill the space allocated for the tab bar. The same behaviour happens on iPads as well and various devices.
Any ideas about how to disable Autolayout or add constraints?

BarButtonItem (iOS11\xCode9) uses autolayout instead of frames. Try this (Swift):
if #available(iOS 9.0, *) {
cButton.widthAnchor.constraint(equalToConstant: customViewButton.width).isActive = true
cButton.heightAnchor.constraint(equalToConstant: customViewButton.height).isActive = true
}
Objective C
if (#available(iOS 9, *)) {
[cButton.widthAnchor constraintEqualToConstant: standardButtonSize.width].active = YES;
[cButton.heightAnchor constraintEqualToConstant: standardButtonSize.height].active = YES;
}

Fallstreak answer (+1) was the solution in my case. I was having a custom view not work on tap. Just wanted to share what I had to do for a custom view in a navigation item to work. This is all swift 3
let backButtonView = BackButtonView.loadNib()
backButtonView?.addTarget(self, action: #selector(returnToPrevious), for: .touchUpInside)
backButtonView.widthAnchor.constraint(equalToConstant: backButtonView.frame.width).isActive = true
backButtonView.heightAnchor.constraint(equalToConstant: backButtonView.frame.height).isActive = true
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButtonView!)
The 2 lines about the anchor width/height came from Fallstreak's answer and were the solution in this case.

Related

Navigation controller background image repeat it self in ios 11 using xcode 9

I had tried following code to set background image of UINavigationBar.
It was working fine before Xcode 9 but in Xcode 9 image are not set properly.
UIImage *image = [UIImage imageNamed:#"headr_bg"];
[self.navigationController.navigationBar setBackgroundImage:image forBarMetrics:UIBarMetricsDefault];
Xcode 9 image 1
Xcode 9 image 2
[before Xcode 9]
may be its not perfect solution &
but i had old project in that i had fixed it by below code. Just updated navigation bar position and navigation view frame for ios version > 11.
CGRect navbarFrame = self.navigationController.navigationBar.frame;
CGRect navFrame = self.navigationController.view.frame;
navbarFrame.size.height = 44;
navFrame.origin.y = 20;
self.navigationController.navigationBar.frame = navbarFrame;
self.navigationController.view.frame = navFrame;
I had the same issue however I could not find a fix working with insets or with a layout based approach. Ultimately a quick work around was to create a secondary background image and pad the top of the original background with 20px with the previous color of your status bar (black in your case).
Then I simply added code to change the image depending if this is iOS 11+ like so:
NSString* navBarImg = #"navbar.png";
if (#available(iOS 11.0, *)) {
navBarImg = #"navbar_ios11.png";
}
[[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:navBarImg] forBarMetrics:UIBarMetricsDefault];
This may not work or may need tweaks depending if the status bar visibility is shown/hidden at some points. Hopefully this will help until a better approach is discovered.

Access the UIImageView of the background image of a UIButton

I have a UIButton, and I would like to access the UIImageView of its background image so that I can make the image circular. I know that I can affect the image itself but I would prefer to do this more elegantly. I also know that I can use the button.currentBackgroundImage property to get the UIImage in the background, but I want the view itself. Once I have the view I intend to use this code:
buttonImageView.layer.cornerRadius = buttonImageView.frame.size.width / 2;
buttonImageView.clipsToBounds = YES;
How can I access the buttonImageView?
EDIT:
Due to #vhristoskov's suggestions, I tried cropping the button itself with this code:
self.button.layer.cornerRadius = self.button.frame.size.width/2;
self.button.clipsToBounds = YES;
And it made this shape:
After debugging I found that frame.size.width was 46, when it should have been 100. So I tried this code instead:
self.button.layer.cornerRadius = self.button.currentBackgroundImage.size.width/2;
self.button.clipsToBounds = YES;
And that produced this shape:
And this time, the cornerRadius was being set to 65. So it actually seems like my problem is that I don't have the correct width at the moment. Which property should I access to get the correct number?
Well as I guessed and as you've already found - the problem is in the button size. To be sure that your button's size at runtime is what you expected to be - review your constraints. In the example below the button has vertical and horizontal central alignment and fixed width and height.
Constraints:
To have perfectly circular button you should have button.width == button.height. If this condition is met your code should work like a charm:
self.button.layer.cornerRadius = CGRectGetWidth(self.button.frame) / 2;
self.button.clipsToBounds = YES;
Assuming that you called something like:
[self.myButton setBackgroundImage:[UIImage imageNamed:#"image"] forState:UIControlStateNormal];
in viewDidLoad or earlier, you can then call the following in viewDidAppear:
if (self.myButton.subviews.count > 0 && [self.myButton.subviews[0] isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = self.myButton.subviews[0];
imageView.layer.cornerRadius = imageView.frame.size.width / 2;
imageView.clipsToBounds = YES;
}
but it is not more elegant as the subview ordering is an implementation detail of UIButton and may change at any time.

UIProgressView track and progress images not working in iOS7

I have a UIProgressView that has been customised with a progress and track image. I also customised the size of the progress view. This works fine in iOS 6. I am facing problems getting this to work in iOS7.
_progress.progressViewStyle = UIProgressViewStyleBar;
_progress.trackImage = [UIImage imageNamed:#"summary-progress-track.png"];
_progress.progressImage = [UIImage imageNamed:#"summary-progress.png"];
_progress.frame = CGRectMake(50, 50, 100, 10);
Not only is the height being ignored but the custom images do not get applied. I just get a blue tinted progress bar like this:
I think the default tint colour is somehow overriding the progress images. I have also tried setting this with UIAppearance but it did not work.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[UIProgressView appearance] setProgressImage:[UIImage imageNamed:#"summary-progress.png"]];
[[UIProgressView appearance] setTrackImage:[UIImage imageNamed:#"summary-progress-track.png"]];
return YES;
}
Personally I use the MPProgressHUD for all progress tracking on a view. Here's the link to the download
https://github.com/jdg/MBProgressHUD
The usage is as simple as it get. Here's a tutorial you might like to check out.
http://code.tutsplus.com/tutorials/ios-sdk-uiactivityindicatorview-and-mbprogresshud--mobile-10530
The MPProgressHUD has a specific method to show custom images.
This should be helpful if you have not already seen this.
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/UIKitUICatalog/UIProgressView.html#//apple_ref/doc/uid/TP40012857-UIProgressView-SW1
the blue color is the default tint color for the control in iOS7 that gets applied.
Found a solution. If anyone else is stuck on this check out this answer and suggested code to workaround the issue: UIProgressView custom track and progress images in iOS 7.1
I print the progress's subviews and find that one of them's frame width is 0,
so after
[_videoProgressView setProgress:progress animated:NO];
reset the track and progress images
UIImageView *trackImageView = _videoProgressView.subviews.firstObject;
UIImageView *progressImageView = _videoProgressView.subviews.lastObject;
CGRect trackProgressFrame = trackImageView.frame;
trackProgressFrame.size.height = _videoProgressView.frame.size.height;
trackImageView.frame = trackProgressFrame;
progressImageView.frame = trackProgressFrame;
progressImageView.image = progressImage;
trackImageView.image = trackImage;

iOS 7 UIButtonBarItem image does not tint

On my nav bar, I have a couple of rightBarButtonItems that have custom icons (the icon images are white, which worked well with the basic color scheme of iOS 6).
Under iOS 7, loading the images using initWithTitle (see code snippet 1) replaces the "white" color in the icon with the proper global tint (a specific color of dark blue in this case)
Code Snippet 1:
UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc] initWithTitle:#"" style:(UIBarButtonItemStyle) UIBarButtonSystemItemCancel target:(self) action:#selector(refreshList)];
refreshButton.image = [UIImage imageNamed:#"RefreshIcon.png"];
However, I needed to use initWithCustomView to overcome a weird change in behavior that was causing the icons to move out of view. The basic idea was to specifically set the size of the icons. initWithCustomView solved the sizing problem, but does not display the button images with the global tint, they are displayed in the color of the image (white). Code Snippet 2 shows how I am creating the button with initWithCustomView.
Code Snippet 2:
CGRect frameCustomButton2 = CGRectMake(0.0, 0.0, 18.0, 18.0);
UIButton *customButton2 = [[UIButton alloc] initWithFrame:frameCustomButton2];
[customButton2 setBackgroundImage:iconRefreshButton forState:UIControlStateNormal];
UIBarButtonItem *barCustomButton2 =[[UIBarButtonItem alloc] initWithCustomView:customButton2 ];
barCustomButton2.image = iconRefreshButton;
[customButton2 addTarget:self action:#selector(refreshList) forControlEvents:UIControlEventTouchUpInside];
All of this code is of course in (void)viewDidLoad. I have tried things like:
barCustomButton2.tintColor = [UIColor blackColor]; //doesn't work
or
[barButtonAppearance setTintColor:[UIColor blackColor]]; // doesn't work
and they do not override the white color of the image. It is almost as if the creation of the custom view takes place after the view looks at the global tint color?
How can I ensure the button icon takes on the global tint?
Thanks!
Just wanted to get this into a root comment to give better context to the "answer" checkmark, and give better formatting.
I was able to figure this one out! You can tell the image to always render as template, which will force it to take on the global tint color.
UIImage *iconRefreshButton = [UIImage imageNamed:#"MyIconFilename.png"];
iconRefreshButton = [iconRefreshButton imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
The default, if you don't set it, is "UIImageRenderingModeAutomatic" which means it will render as a template or original image based on context.
You'll either have to work around the issue you were having with the first code snippet, or you'll have to create a UIButton subclass that uses its image as a mask to show the tint color in drawRect:.
I'd recommend the first approach.

UIButton doesn't listen to content mode setting?

firstButton is a UIButton of type Custom. I'm programmatically putting three of them across each cell of a table, thusly:
[firstButton setImage:markImage forState:UIControlStateNormal];
[firstButton setContentMode:UIViewContentModeScaleAspectFit];
[cell.contentView addSubview:firstButton];
Elsewhere, I'm telling it to clipToBounds. What I get is a crop of the center square of the image, rather than an aspect-scaled rendering of it. I've tried this lots of ways, including setting the mode property on firstButton.imageView, which also doesn't seem to work.
I had the same problem. I see this question is a little old, but I want to provide a clear and correct answer to save other folks (like me) some time when it pops up in their search results.
It took me a bit of searching and experimenting, but I found the solution. Simply set the ContentMode of the "hidden" ImageView that is inside the UIButton.
[[firstButton imageView] setContentMode: UIViewContentModeScaleAspectFit];
[firstButton setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];
Perhaps that's what Dan Ray was alluding to in his accepted answer, but I suspect not.
If you're dealing with the UIButton's image (as opposed to it's backgroundImage), setting the contentMode on the UIButton itself or on its imageView has no effect (despite what other answers say).
Alternatively do this instead:
self.button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
self.button.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
Or size your image accordingly.
OR just use a UIImageView (which properly respects contentMode) with a UITapGestureRecognizer attached to it, or a transparent UIButton on top of it.
Rather than setting the contentMode on the button itself, you'll want to set contentHorizontalAlignment and contentVerticalAlignment properties and (crucially) the contentMode for the button's imageView for any kind of aspect fill or fit:
button.contentHorizontalAlignment = .fill
button.contentVerticalAlignment = .fill
button.imageView?.contentMode = .scaleAspectFit
You can also do other things like aligning the button's image to the top. If you don't need an aspect fill or fit, you just can set the alignment by itself:
button.contentVerticalAlignment = .top
After a couple of hours of confusion, here's how I got it to work under iOS 3.2. As dusker mentioned, using setBackgroundImage instead of setImage did the job for me.
CGRect myButtonFrame = CGRectMake(0, 0, 250, 250);
UIImage *myButtonImage = [UIImage imageNamed:#"buttonImage"];
UIButton *myButton = [UIButton buttonWithType:UIButtonTypeCustom];
[myButton setBackgroundImage:myButtonImage forState:UIControlStateNormal];
[myButton setFrame: myButtonFrame];
[myButton setContentMode: UIViewContentModeScaleAspectFit];
The answer is to use a UIImageView with all the lovely Content Mode settings you want, and then layer a custom button on top of it. Dumb that you can't do that all in one shot, but it appears that you can't.
These two things (which are quite hard to find initially) will stretch your UIButton image to fit the button size:
one should always try to set such in the Storyboard rather than code.
Found a fix for this. Set the adjustsImageWhenHighlighted property of UIButton to NO.
UIButton *b = [[UIButton alloc] initWithFrame:rect];
[b setImage:image forState:UIControlStateNormal];
[b.imageView setContentMode:UIViewContentModeScaleAspectFill];
[b setAdjustsImageWhenHighlighted:NO];
Hope this helps. Feel free to comment below, I will follow up on any questions that you have.
My answer is similar to Kuba's. I needed my image to be programatically set.
UIImage *image = [[UIImage alloc] initWithContentsOfFile:...];
[button setBackgroundImage:image forState:UIControlStateNormal];
button.imageView.contentMode = UIViewContentModeScaleAspectFill; //this is needed for some reason, won't work without it.
for(UIView *view in button.subviews) {
view.contentMode = UIViewContentModeScaleAspectFill;
}
Only solution which worked for me:
[button setImage:image forState:UIControlStateNormal];
button.imageView.contentMode = UIViewContentModeScaleAspectFill;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
button.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
Swift 3
self.firstButton.imageView?.contentMode = .scaleAspectFill
For anyone experiencing this on iOS 15 and Xcode 13, see Matt's answer in this other question.
The behavior of Xcode changed and now defaults UIButtons from the library to the plain style, which prevents the child image from scaling as expected.
Instead of setImage try setBackgroundImage
I believe we have a simple interface builder issue here - apparently the IB ignores any content-mode changes AFTER you have set the image-property.
the solution is as simple: set the content mode, remove previously set image-names (make sure you remove it in all states, default, highlighted etc.), then re-enter the desired image-names in all desired states - et voilà.
I also advice to have a look at the adjustsImageWhenHighlighted UIButton property to avoid weird deformations of the image, when the button is pressed.
In trying to figure this out, my method got a bit hackier as time went on, and I wound up subclassing UIButton and overriding setHighlighted:
For me it works to just knock down the image alpha to .5, because they're on a black background.
However, it only works if I comment out [super setHighlighted:] (where it appears the image-stretchy code is going on), which just doesn't feel like the right way to solve this at all...everything seems to be working fine, though. We'll see how it holds up as I keep working on it.
- (void)setHighlighted:(BOOL)highlight {
if (highlight) {
[self.imageView setAlpha:.5];
} else {
[self.imageView setAlpha:1];
}
// [super setHighlighted:highlight];
}
If anyone looking for answer that work in iOS 6 and iOS 7 and storyboard:
You can set image in your storyboard:
And then:
for(UIView* testId in self.subviews) {
if([testId isKindOfClass:[UIImageView class]])
[testId setContentMode:UIViewContentModeScaleAspectFill];
}
If the UIButton does not seem to listen to the layout constraint settings, do check whether the images are larger than the button size. Always use the #2x and #3x images for retina resolutions.

Resources