No Shadow/Emboss on UIBarButtonItem - ios

I've got a problem with an custom UIBarButtonItem. When I create an custom UIBarButtonItem via
[[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:#"FilterIcon.png"] style:UIBarButtonItemStyleBordered target:self action:#selector(filterTouched:)];
the resulting button does not have the "embossed" look, that the system items achieve by placing a semi-transparent black shadow behind their icons.
On the left you see the "Organize" system bar button item, rightthe result of the code from above.
Creating the shadow in the resource is futile, because iOS/Cocoa only used the mask of the image and discards any color information.
Interestingly, if I create the bar button item in the Interface-Builder it looks fine. However, in the context of my problem, I need to create the button item in code.

There is Objective-C version of James Furey's script.
- (UIImage *)applyToolbarButtonStyling:(UIImage *)oldImage {
float shadowOffset = 1;
float shadowOpacity = .54;
CGRect imageRect = CGRectMake(0, 0, oldImage.size.width, oldImage.size.height);
CGRect shadowRect = CGRectMake(0, shadowOffset, oldImage.size.width, oldImage.size.height);
CGRect newRect = CGRectUnion(imageRect, shadowRect);
UIGraphicsBeginImageContextWithOptions(newRect.size, NO, oldImage.scale);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -(newRect.size.height));
CGContextSaveGState(ctx);
CGContextClipToMask(ctx, shadowRect, oldImage.CGImage);
CGContextSetFillColorWithColor(ctx, [UIColor colorWithWhite:0 alpha:shadowOpacity].CGColor);
CGContextFillRect(ctx, shadowRect);
CGContextRestoreGState(ctx);
CGContextClipToMask(ctx, imageRect, oldImage.CGImage);
CGContextSetFillColorWithColor(ctx, [UIColor colorWithWhite:1 alpha:1].CGColor);
CGContextFillRect(ctx, imageRect);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}

I think the reason this occurs is covered by these answers to another question:
https://stackoverflow.com/a/3476424/1210490
https://stackoverflow.com/a/6528603/1210490
UIBarButtonItems behave differently depending on where you programmatically attach them. If you attach them to a toolbar, they'll become white "embossed" icons. If you attach them to a navigation bar, they won't.
I've spent the last few hours writing a function to apply toolbar UIBarButtonItem styling to UIImages. It's written in C# for MonoTouch, but I'm sure you'll be able to tweak it to Obj-C no problemo...
UIImage ApplyToolbarButtonStyling(UIImage oldImage)
{
float shadowOffset = 1f;
float shadowOpacity = .54f;
RectangleF imageRect = new RectangleF(PointF.Empty, oldImage.Size);
RectangleF shadowRect = new RectangleF(new PointF(0, shadowOffset), oldImage.Size);
RectangleF newRect = RectangleF.Union(imageRect, shadowRect);
UIGraphics.BeginImageContextWithOptions(newRect.Size, false, oldImage.CurrentScale);
CGContext ctxt = UIGraphics.GetCurrentContext();
ctxt.ScaleCTM(1f, -1f);
ctxt.TranslateCTM(0, -newRect.Size.Height);
ctxt.SaveState();
ctxt.ClipToMask(shadowRect, oldImage.CGImage);
ctxt.SetFillColor(UIColor.FromWhiteAlpha(0f, shadowOpacity).CGColor);
ctxt.FillRect(shadowRect);
ctxt.RestoreState();
ctxt.ClipToMask(imageRect, oldImage.CGImage);
ctxt.SetFillColor(UIColor.FromWhiteAlpha(1f, 1f).CGColor);
ctxt.FillRect(imageRect);
UIImage newImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return newImage;
}
So, a UIBarButtonItem that used to look like this:
Created instead with the function above, like this:
UIBarButtonItem barButtonItem = new UIBarButtonItem(ApplyToolbarButtonStyling(UIImage.FromFile("MusicIcon.png")), UIBarButtonItemStyle.Plain, delegate {});
Would now look like this:
Hope this helps someone in the future.

Take note of the shadow offset in James Furey's script.
I've made the following experience:
float shadowOffset = 1.0f // for a UIBarButtonItem in UINavigationItem
float shadowOffset = 0.0f // for a UIBarButtonItem in UIToolBar
This was observed with iOS 6.1 SDK.
(Now obsolete under iOS 7)

Related

Set Background Color on UIButton when Disabled?

Is it possible to set the background color of a disabled UIButton? We'd like to use a more muted color when the button is disabled, but I do not see any methods within UIButton that would allow this. Is there a workaround or another way we could accomplish this?
I am concentrating on this comment of your's -"We'd like to use a more muted color when the button is disabled". If changing the alpha of the button is an acceptable solution you can very well use this code.
someButton.alpha = 0.5;//This code should be written in the place where the button is disabled.
The best way to work with button, in my honest opinion, is subclassing and customize UI, like in example:
class XYButton: UIButton {
override public var isEnabled: Bool {
didSet {
if self.isEnabled {
self.backgroundColor = self.backgroundColor?.withAlphaComponent(1.0)
} else {
self.backgroundColor = self.backgroundColor?.withAlphaComponent(0.5)
}
}
}
}
Then you can use XYButton either in your storyboards/xibs or programmatically.
** more elegant:
didSet {
self.backgroundColor = self.backgroundColor?.withAlphaComponent(isEnabled ? 1.0 : 0.5)
}
Seems that this is not possible as the background color property is controlled by the UIView class which is the parent of UIButton.
So UIButton control state doesn't own this property and doesn't control it.
But still you can inherit UIButton and overwrite the setEnabled method to update the color accordingly.
This is the most clear way to do it. as you can assign the inherited class to any UIButton in storyboard or Xib file. without the need for any connections. (no IBOutlets)
Hope this is helpful, if you wish I can share some code.
Hope this helps :
UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 100, 35)];
CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [[UIColor redColor] CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[btn setBackgroundImage:image forState:UIControlStateDisabled];
This code is for Swift 2
let rect = CGRectMake(0, 0, someButton.frame.width,someButton.frame.height)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColor(context, CGColorGetComponents(UIColor.lightGrayColor().CGColor))
CGContextFillRect(context, rect)
UIGraphicsBeginImageContextWithOptions(view.bounds.size, true, 0)
view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
someButton.setBackgroundImage(image, forState: .Disabled)
someButton.enabled = false
As suggested here:
https://stackoverflow.com/a/5725169/1586606 Set alpha property like;
myButton.alpha = 0.5

iOS7 UINavigationBar subclass > try to draw under UIStatusBar

I tried to draw a custom UINavigationBar via drawRect on iOS7. With the changes of the Navigationbar and the Statusbar my draw begins on y-origin 20.0, not on my 0.0 behind the Statusbar. I checked the wwdc videos but I only found examples with images not with custom draw. Any ideas? Do I need to set some parameters in my subclass?
create a new project based on "Master-Detail Application"
create a sublass for UINavigationBar
change the UINavigationBar in Main.storyboard to the custom class
turn off translucent [self setTranslucent:NO];
add a real simple drawRect to the sublass of UINavigationBar
I made a simple test:
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
UIBezierPath* maskPath = [UIBezierPath bezierPathWithRect: CGRectMake(0, 0, 320, 64)];
[maskPath closePath];
[[UIColor greenColor] setFill];
[maskPath fill];
CGColorSpaceRelease(colorSpace);
The green NavigationBar begins under the Statusbar, how can I draw by starting behind the Statusbar?
CURRENT SOLUTION:
#Redwarp posted one way, I made also a simple version to test:
CustomBGView *testView = [[CustomBGView alloc] initWithFrame:CGRectMake(0, 0, 320, 64)];
RetinaAwareUIGraphicsBeginImageContext(testView.frame.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[testView.layer renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self setBackgroundImage:image forBarPosition:UIBarPositionTop barMetrics:UIBarMetricsDefault];
Inside the custom UIView you can draw like you want and your custom view appears also behind the StatusBar.
I found a solution, which is a bit peculiar : I subclass the UINavigationBar, and in the onLayoutSubviews, I create the background I want to create, and set it as background image. So I do the equivalent of the drawRect, but in a UIImage that I set as background.
Then, in the xib or storyboard, I select my subclass as the type of the Navigation bar.
Here goes :
#implementation MyNavigationBar
- (void)layoutSubviews {
[super layoutSubviews];
CGPoint origin = [self.superview convertPoint:self.frame.origin toView:nil];
UIGraphicsBeginImageContextWithOptions(CGSizeMake(self.bounds.size.width, self.bounds.size.height + origin.y), NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClipToRect(context, CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height + origin.y));
[self.baseImage drawInRect:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
UIImage *backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self setBackgroundImage:backgroundImage forBarMetrics:UIBarMetricsDefault];
// If you don't want the bar shadow, set the shadow image as null
[self setShadowImage:[UIImage new]];
}
And whenever you wan't to change the image, you call [navigationBar needsLayout], and it will call layoutSubviews again
EDIT : The trick is getting the origin of the nav bar. If there is a status bar, the origin will be of y 20. If no status bar, it will be 0. So you add the origin.y to the height of the image your drawing into, and you will have a nice result.

iOS UINavigation "Back" button Bezier Path

In my app I have custom gradients I use to stylize all instances of UIBarButtonItem, accomplished by creating a gradient, clipping the gradient with a rounded rectangle Bezier path, then creating a resizable UIImage, like so:
CGRect rect = CGRectMake(0, 0, 44, 44);
CGSize size = rect.size;
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
size_t gradientNumberOfLocations = 2;
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef btncontext = UIGraphicsGetCurrentContext();
CGFloat btngradientLocations[2] = { 0.2, 1.0 };
CGFloat btngradientComponents[8] = { 0.10, 0.10, 0.10, 1.0, 0.05, 0.05, 0.05, 1.0 };
CGGradientRef btngradient = CGGradientCreateWithColorComponents (colorspace, btngradientComponents, btngradientLocations, gradientNumberOfLocations);
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:4.0f];
[path addClip];
CGContextDrawLinearGradient(btncontext, btngradient, CGPointMake(0, 0), CGPointMake(0, size.height), 0);
UIImage *btnimage = UIGraphicsGetImageFromCurrentImageContext();
CGGradientRelease(btngradient);
UIGraphicsEndImageContext();
CGColorSpaceRelease(colorspace);
UIImage *navButton = [btnimage resizableImageWithCapInsets:UIEdgeInsetsMake(22.0, 8.0, 22.0, 8.0)];
[[UIBarButtonItem appearance] setBackgroundImage:navButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
This works just fine, and I am developing a similar method to stylize the back button via -setBackButtonBackgroundImage, however I have a question:
I would like to know if anyone has knows a proper way to create a UIBezierPath object that represents the outline of the back button, such that I can create a gradient and programmatically clip the gradient to a back button shape (just like in the example) without having to use a PNG or some such resource.
Any help is appreciated, thank you.

Create UIImage from shadowed view while retaining alpha?

I have a somewhat unusual problem. In my app, I am shadowing a UIImageView using basic Quartz2d layer shadowing. Here's my code:
imageView.layer.shadowOpacity = 1.0; // Pretty self explanatory
imageView.layer.shadowRadius = 8.0; // My softness
imageView.layer.shadowColor = [UIColor blackColor].CGColor; // Color of the shadow
imageView.layer.shadowOffset = CGSizeMake(0.0, 0.0); // Offset of the shadow
This produces a nice blur behind the image view. However, I am doing some animation with this view, and the constant recalculation of the shadowing during the transitions causes a very choppy transition. What I'd like to be able to do is create a UIImage or .png file out of the image view with the blur and its alpha intact. Here's what I've already tried:
UIGraphicsBeginImageContext(CGSizeMake(320, 396));
[imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(viewImage, nil, nil, nil);
Since I have a shadow which "grows outside" of the image view, I can't just pass his size in the UIGraphicsBeginImageContext function. I set the correct size, but the resulting image doesn't save my alpha, instead it places a white background behind the shadow, which won't work for me because my real background is a wood texture. Also, the view isn't centered in the resulting file.
I'm pretty good with UIKit, but I'm a real noobie with Quartz 2d graphics, so if there's an obvious answer to this, send it anyway. :)
Try setting your UIImageView's backgroundColor to [UIColor clearColor] - this may enable your current solution to work.
imageView.backgroundColor = [UIColor clearColor];
UIGraphicsBeginImageContext(CGSizeMake(320, 396));
[imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Source: Converting a CGLayer to a *transparent* UIImage and PNG file in CoreGraphics iphone
I've had this issue before too. My solution was not to do an image, but instead to set the shadow path to just the outline of the view you are animating.
[imageView setContentMode:UIViewContentModeScaleAspectFit];
imageView.layer.masksToBounds = NO;
imageView.layer.shadowOffset = CGSizeMake(5, 5);
imageView.layer.shadowRadius = 200;
imageView.layer.shadowOpacity = 0.5;
CGRect rect = [self rectFromImageView:imageView];
imageView.layer.shadowPath = [UIBezierPath bezierPathWithRect:rect].CGPath;
Which uses the following function which assumes the image is set to content mode aspect fit:
-(CGRect)rectFromImageView:(UIImageView *)iv {
CGRect rect = (CGRect){{0,0},iv.frame.size};
if (iv.image.size.width/iv.frame.size.width > iv.image.size.height/iv.frame.size.height) {
//rect.origin.x == 0
CGFloat sf = iv.frame.size.width/iv.image.size.width;
rect.size.height = sf*iv.image.size.height;
rect.origin.y = floor((iv.frame.size.height - rect.size.height)/2);
} else {
//rect.origin.y == 0;
CGFloat sf = iv.frame.size.height/iv.image.size.height;
rect.size.width = sf*iv.image.size.width;
rect.origin.x = floor((iv.frame.size.width - rect.size.width)/2);
}
return rect;
}
If your image is just set to fill then just using the imageView.bounds should be sufficient
Add shadow path to view like this
[view.layer setShadowPath:[[UIBezierPath bezierPathWithRect:view.bounds] CGPath]]; //add path
view.layer.shadowColor = [[UIColor blackColor] CGColor];
view.layer.shadowOffset = CGSizeMake(x, y);
view.layer.shadowRadius = rad;
view.layer.shadowOpacity = 0.2f;

UIToolBar with reduced alpha, want UIBarButtonItem to have alpha 1

I have a UIToolbar with the alpha set to .6. Looks great - but it leaves the buttons with an alpha of .6 as well, which is okay but not really desirable. Is there some way to independently set the alpha of a UIBarButtonItem? How would I get these to look white instead of slightly grayed out?
When you set the alpha on a view, all subviews are composited with that same alpha value. What you can do instead of setting the alpha on the view itself is use a tint color or background color with an alpha value. This can produce similar effects, without causing all subviews to inherit the transparency.
toolbar.tintColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
If this doesn't work and you actually need to make the view itself transparent, you will have to rework your view hierarchy so that the opaque views are not subviews. Since bar button items aren't available as standard views, its probably not going to be workable in the toolbar case.
self.bottomToolbar.barStyle = UIBarStyleBlack;
self.bottomToolbar.translucent = YES;
self.bottomToolbar.backgroundColor = [UIColor clearColor];
View opacity set to 1 and these settings did it for me.
Above did not work for me in iOS 4.3, but this did:
Subclass UIToolbar, provide one method:
- (void)drawRect:(CGRect)rect
{
[[UIColor colorWithWhite:0 alpha:0.6f] set]; // or clearColor etc
CGContextFillRect(UIGraphicsGetCurrentContext(), rect);
}
I've found that barStyle does'n actually matter. Neither is setting background color to clear.
self.toolbar.barStyle = UIBarStyleDefault;
self.toolbar.tintColor = [UIColor colorWithWhite:0.0 alpha:0.5];
self.toolbar.translucent = YES;
Those lines done the job for me(ios4.3).
As I can understand, setting toolbars transfluent property to YES you've setting it to be with clear(transparent) background. Of course it depends on tint color you've set. If it doesn't contain alpha component the bar will be opaque.
The main issue with controlling the alpha value of the toolbar is that it applies to the entire toolbar and its buttons. If you set a backgroundColor with some alpha value < 1, the resulting colour is combined with the barStyle (UIBarStyleDefault or UIBarStyleBlack) which results in a more opaque colour than the original backgroundColor. If you set the barTintColor, it seems to override all other settings, including any alpha value you set, resulting in a completely opaque colour.
The only way I've been able to do reduce the alpha of the toolbar without affecting its buttons is by setting its background image via setBackgroundImage:forToolbarPosition:barMetrics:. You can create a 1px by 1px image with the desired colour and alpha:
+ (UIImage *)onePixelImageWithColor:(UIColor *)color {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, 1, 1, 8, 0, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedFirst);
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
CGImageRef imgRef = CGBitmapContextCreateImage(context);
UIImage *image = [UIImage imageWithCGImage:imgRef];
CGImageRelease(imgRef);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return image;
}
[Swift version]
Then set the background Image:
[toolbar setBackgroundImage:[self onePixelImageWithColor:[[UIColor blackColor] colorWithAlphaComponent:0.7]]
forToolbarPosition:UIBarPositionBottom
barMetrics:UIBarMetricsDefault];
After many hours evaluating each methods above, I found only the method provided by Ryan H. is feasible. It is because this is the method you can manipulate the alpha value while others cannot.
Here is the version for Swift 3:
override func viewDidLoad() {
super.viewDidLoad()
let bgImageColor = UIColor.black.withAlphaComponent(0.6) //you can adjust the alpha value here
self.upperToolBar.setBackgroundImage(onePixelImageWithColor(color: bgImageColor),
forToolbarPosition: .any,
barMetrics: UIBarMetrics.default)
}
func onePixelImageWithColor(color : UIColor) -> UIImage {
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
var context = CGContext(data: nil, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
context!.setFillColor(color.cgColor)
context!.fill(CGRect(x:0,y: 0, width: 1, height:1))
let image = UIImage(cgImage: context!.makeImage()!)
return image
}

Resources