Custom TabBar on iOS5 with API - cocoa-touch

I tried the new Customizing API for iOS 5 and have some problems I don`t understand. The way I do it:
UITabBar *tabBar = [rootController tabBar];
if ([tabBar respondsToSelector:#selector(setBackgroundImage:)])
{
[tabBar setBackgroundImage:[UIImage imageNamed:#"tabbar_bg.png"]];
tabBar.selectionIndicatorImage = [UIImage imageNamed:#"over.png"];
tabBar.tintColor = [UIColor colorWithRed:56.0/255.0 green:63.0/255.0 blue:74.0/255.0 alpha:1.0];
tabBar.selectedImageTintColor = [UIColor colorWithRed:94.0/255.0 green:102.0/255.0 blue:114.0/255.0 alpha:1.0];
}
The problem is shown on the image below:
The border ist my Problem... and it only occurs if I try to use it with nice ( :P ) colors.. if I try it with white it looks like this:
Do you have any ideas how to fix it?

If you create a subclass of UITabBarItem and implement the methods
- (UIImage *)selectedImage
- (UIImage *)unselectedImage
You can return whatever images you want from these and they won't have any styling effects applied.
Technically these are private methods, but you aren't calling them, you are overriding them, and I've seen plenty of apps use this technique without being rejected.
You can also use a category to override these methods for all tabbaritems in your app. A good trick is to just override selectedImage to return image, like this:
- (UIImage *)selectedImage
{
return self.image;
}
That way, all of your tab bar items will use whatever image you supply without applying any effects for the selectedImage, but will still use the default grey styling for the unselectedImage. Note that this means that you supply an image with colours for the tab bar items, not just a mask image as normal.

Related

Custom NSView background color not changing when switching in and out of dark mode

I cannot figure out how to update the background color of my custom NSView when the user switches in and out of dark mode.
I've read the documentation and followed instructions here:detecting darkmode
The strange thing is that I can get all the subviews to behave correctly, but for some strange reason I can't get the background color of the main view to change. The background color of the view looks correct when I start the app in either mode, but when I switch between modes while the app is running it doesn't update to the new theme.
Would be grateful for any suggestions.
Inside the Custom NSView I have the method
- (void) viewDidChangeEffectiveAppearance
{
self.needsDisplay = YES;
}
and inside the drawRect I have a do a simple color change before continuing with drawing in the view
NSAppearance *currentAppearance = [NSAppearance currentAppearance];
if (#available(*, macOS 10.14)) {
if(currentAppearance.name == NSAppearanceNameDarkAqua) {
red = 0.5*red+0.5;
green = 0.5*green+0.5;
blue = 0.5*blue+0.5;
}
}
Here is a screenshot of darkmode before (the way it should look)
Dark Mode Before
Here is a screenshot of light mode after user switch
Light Mode After
Here is a screenshot of lightmode before (the way it should look)
Light Mode Before
And here is a screenshot of darkmode after user switch
Dark Mode After
ps The reason I'm baffled and have little code to post is that the correct behavior is supposed to happen automatically with little effort. I even deleted the view from the nib and rebuilt it thinking maybe some setting got corrupted, but that didn't solve the problem.
Update: I found the source of the problem. This method gets called in windowDidLoad
- (void) setTransparent:(BOOL)transparent
{
if(transparent) {
[self.window setOpaque:NO];
NSColor *backgroundColor = [NSColor windowBackgroundColor];
backgroundColor = [backgroundColor colorWithAlphaComponent: .75];
[self.window setBackgroundColor:backgroundColor];
self.window.alphaValue = 0.75;
}
else {
[self.window setOpaque:YES];
NSColor *backgroundColor = [NSColor windowBackgroundColor];
backgroundColor = [backgroundColor colorWithAlphaComponent: 1];
[self.window setBackgroundColor:backgroundColor];
self.window.alphaValue = 1;
}
}
I get the expected behavior if I comment out the call to this method.
Why did this cause me to lose the automatic behavior of background color change when the user changes between light and dark mode?
My guess is that you’re not actually using the standard color for the background color.
You are using [NSColor windowBackgroundColor], however then making a copy with a different alpha component (via colorWithAlphaComponent), making it no longer a standard color.
My guess is a lot of the automatic ‘just works’ behaviour happens when you use the standard color definitions. As a test, could you try removing the colorWithAlphaComponent calls (where you are adding the transparency) from your settransparent method and see whether it works? if it does, you might need to find another way to add transparency to your view if you want the automatic behaviour.

NSStatusItem change image for dark tint

With OSX 10.10 beta 3, Apple released their dark tint option. Unfortunately, it also means that pretty much all status bar icons (with the exception of Apple's and Path Finder's that I've seen), including mine, remain dark on a dark background. How can I provide an alternate image for when dark tint is applied?
I don't see an API change on NSStatusBar or NSStatusItem that shows me a change, I'm assuming it's a notification or something reactive to easily make the change as the user alters the tint.
Current code to draw the image is encased within an NSView:
- (void)drawRect:(NSRect)dirtyRect
{
// set view background color
if (self.isActive) {
[[NSColor selectedMenuItemColor] setFill];
} else {
[[NSColor clearColor] setFill];
}
NSRectFill(dirtyRect);
// set image
NSImage *image = (self.isActive ? self.alternateImage : self.image);
_imageView.image = image;
}
TL;DR: You don't have to do anything special in Dark Theme. Give NSStatusItem (or NSStatusBarButton) a template image and it will style it correctly in any menubar context.
The reason why some apps' status items (such as PathFinder's) already work in Dark Theme is because they're not setting their own custom view on the StatusItem, but only setting a template image on the StatusItem.
Something like:
_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
NSImage *image = [NSImage imageNamed:#"statusItemIcon"];
[image setTemplate:YES];
[_statusItem setImage:image];
This works exactly as you'd expect in Mavericks and earlier, as well as Yosemite and any future releases because it allows AppKit to do all of the styling of the image depending on the status item state.
Mavericks
In Mavericks (and earlier) there were only 2 unique styles of the items. Unpressed and Pressed. These two styles pretty much looked purely black and purely white, respectively. (Actually "purely black" isn't entirely correct -- there was a small effect that made them look slightly inset).
Because there were only two possible state, status bar apps could set their own view and easily get the same appearance by just drawing black or white depending on their highlighted state. (But again note that it wasn't purely black, so apps either had to build the effect in the image or be satisfied with a hardly-noticeable out of place icon).
Yosemite
In Yosemite there are at least 32 unique styling of items. Unpressed in Dark Theme is only one of those. There is no practical (or unpractical) way for an app to be able to do their own styling of items and have it look correct in all contexts.
Here are examples of six of those possible stylings:
Status items on an inactive menubar now have a specific styling, as opposed to a simple opacity change as in the past. Disabled appearance is one other possible variation; there are also other additional dimensions to this matrix of possibilities.
API
Arbitrary views set as NSStatusItem's view property have no way to capture all of these variations, hence it (and other related API) is deprecated in 10.10.
However, seed 3 introduces new API on NSStatusItem:
#property (readonly, strong) NSStatusBarButton *button NS_AVAILABLE_MAC(10_10);
This piece of API has a few purposes:
An app can now get the screen position (or show a popover from) a status item without setting its own custom view.
Removes the need for API like image, title, sendActionOn: on NSStatusItem.
Provides a class for new API: i.e. looksDisabled. This allows apps to get the standard disabled/off styling (like Bluetooth/Time Machine when off) without requiring a custom image.
If there's something that can't be done with the current (non- custom view) API, please file an enhancement request for it. StatusItems should provide behavior or appearances in a way that it standard across all status items.
More discussion is at https://devforums.apple.com/thread/234839, although I've summarized most everything here.
I end up did something like following to my custom drag and drop NSStatusItemView: (Using Swift)
var isDark = false
func isDarkMode() {
isDark = NSAppearance.currentAppearance().name.hasPrefix("NSAppearanceNameVibrantDark")
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
isDarkMode()
// Now use "isDark" to determine the drawing colour.
if isDark {
// ...
} else {
// ...
}
}
When the user changed the Theme in System Preferences, the NSView will be called by the system for re-drawing, you can change the icon colour accordingly.
If you wish to adjust other custom UI outside this view, you can either use KVO to observer the isDark key of the view or do it on your own.
I created a basic wrapper around NSStatusItem that you can use to provide support for 10.10 and earlier with custom views in the status bar. You can find it here: https://github.com/noahsmartin/YosemiteMenuBar The basic idea is to draw the custom view into a NSImage and use this image as a template image for the status bar item. This wrapper also forwards click events to the custom view so they can be handled the same way as pre 10.10. The project contains a basic example of how YosemiteMenuBar can be used with a custom view on the status bar.
Newest swift code set image template method is here:
// Insert code here to initialize your application
if let button = statusItem.button {
button.image = NSImage(named: "StatusIcon")
button.image?.isTemplate = true // Just add this line
button.action = #selector(togglePopover(_:))
}
Then it will change the image when dark mode.
When your application has drawn any GUI element you can get its appearance via [NSAppearance currentAppearance] which itself has a name property that holds something like
NSAppearanceNameVibrantDark->NSAppearanceNameAqua->NSAppearanceNameAquaMavericks
The first part is the appearance’s name, which is also available as a constant in NSAppearanceNameVibrantDark or NSAppearanceNameVibrantLight.
I don’t know if there’s a way to get just the first part, but I think this does the trick for now.
Example code:
-(void)awakeFromNib {
NSStatusItem* myStatusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
myStatusItem.title = #"Hello World";
if ([[[NSAppearance currentAppearance] name] containsString:NSAppearanceNameVibrantDark]) {
myStatusItem.title = #"Dark Interface";
} else {
myStatusItem.title = #"Light Interface";
}
}
But just in case you do want to monitor the status changes you can. I also know there is a better way to determine lite/dark mode than what's been said above, but I can remember it right now.
// Monitor menu/dock theme changes...
[[NSDistributedNotificationCenter defaultCenter] addObserver: self selector: #selector(themeChange:) name:#"AppleInterfaceThemeChangedNotification" object: NULL];
//
-(void) themeChange :(NSNotification *) notification
{
NSLog (#"%#", notification);
}

UITabBarItem: Overwrite default appearance in subclass

I have a weird problem, let me explain: I use [UITabBarItem appearance] to change the font and color of all UITabBarItems in my app. This works like a charm and all UITabBarItems are styled correctly.
The code is:
// Set the normal state
[[UITabBarItem appearance] setTitleTextAttributes:
#{
UITextAttributeTextColor: AUIColorObject
} forState:forState:UIControlStateHighlighted];
Now I want to overwrite that style for a single UITabBar. I extended my UITabBar subclass to handle something like styles, in my case possible values are RootTabBarControllerStyleDefault and RootTabBarControllerStyleBox.
How can I set the TitleTextAttributes for this single UITabBarItem? I use as subclassed UITabBarController and a UITabBar for maximum control.
More detailed, this is my working workaround:
1) UITabBar has as static method to set the appearance, like this:
+ (void)setAppearinaceForStyle:(RootTabBarControllerStyle)_style
{
[[UITabBarItem appearance] setTitleTextAttributes:
#{
UITextAttributeTextColor: (_style == RootTabBarControllerStyleDefault ? UIColor1 : UIColor2)
} forState:UIControlStateHighlighted];
}
2) Then I set up a delegate for my UITabBarController doing the following stuff:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
[RootTabBar setAppearinaceForStyle:RootTabBarControllerStyleBox];
[tabBarController.tabBar setNeedsDisplay];
for (UITabBarItem *item in tabBarController.tabBar.items)
{
NSString *oldString = item.title;
// Change the title string to force a redraw
[item setTitle:[NSString stringWithFormat:#"%# ", item.title]];
// Set the title back to its default value
[item setTitle:oldString];
}
[RootTabBar setAppearinaceForStyle:RootTabBarControllerStyleDefault];
}
This allows me to set the different style for this single UITabBar but I hope there is a better/cleaner way to do this.
So you want to customize differently one of your UITabBarItem's?
If so, you already have a subclass for that single item, just us [MyTabBarItemSubclass appearance] instead of the regular [UITabBarItem appearance] used for the other items.
The [UIAppearance documentation](
https://developer.apple.com/library/ios/documentation/uikit/reference/UIAppearance_Protocol/Reference/Reference.html) states that:
Use the UIAppearance protocol to get the appearance proxy for a class.
You can customize the appearance of instances of a class by sending
appearance modification messages to the class’s appearance proxy.
Note: iOS applies appearance changes when a view enters a window, it
doesn’t change the appearance of a view that’s already in a window. To
change the appearance of a view that’s currently in a window, remove
the view from the view hierarchy and then put it back.
You should play around with view hierarchy to remove the view that contains the tabbar and putting it again. However, your method is making more or less the same job (a bit less elegantly thus).
I think that te result will be the same if you follow Apple's doc

UIPickerView background color not changed?

I want to change the picker view bakground color. I try this way but not worked.
doublePicker.backgroundColor = [self RGBColorR:85 G:17 B:92];
- (UIColor *)RGBColorR:(double)red G:(double)green B:(double)blue {
return [UIColor colorWithRed:(red/255.00) green:(green/255.00) blue:(blue/255.00) alpha:1.00];
}
I want to do picker view like in picture. How can I do this? Thanks for your reply.
you can add subviews over certain areas of your pickerview..
use:
[picker addSubview: coverView]; //adding subviews to different area of the picker
you're going to find yourself playing with alot of CGRect to get thing to fit properly. If you want to change the entire thing you're going to have to override some methods that handle the touch events etc..
this tutorial might help you create a custom picker
https://developer.apple.com/iphone/library/samplecode/UICatalog/
You cannot change the appearance of UIPickerView, even the size, it's the most unchangeable UI element in iOS. Best you can do is build custom by yourself, using UIScrollView with paging enabled.

Optionally set navigationbar background image

I have the need to draw a background image or set a tint color on a navigation bar, but I also need the option to have the navigation bar appear as it normally would. I'm currently using a category to support If my app does not specify a background image, what can I do instead to ensure the drawRect method does it normally would do?
I.E. -
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
if(hasImage){
UIImage *img = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://myimageurl.com/img.jpg"]]];
[img drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}else{
??????
}
}
#end
I actually ended up doing something entirely different and I'm wondering why nobody hasn't discovered this before. One of the approaches I've seen in the course of my Googling on the subject was simply adding an image as a subview to the UINavigationBar. The only problem was this made the buttons in the bar not clickable. The fix was to disable user interaction on the image.
myUIImageView.userInteractionEnabled = NO;
[myNavController.navigationBar addSubview:myUIImageView];
[myNavController.navigationBar sendSubviewToBack:myUIImageView];
With that, everything looks/works great and I don't have to override the drawRect method with a category, swizzle methods or any of that funky stuff. Simple and clean.
Theoretically you could do this by subclassing UINavigationBar overriding only the drawRect: method, and then calling [super drawRect:rect] when you want to use the default behavior.
But I don't believe you can in practice because you don't instantiate the UINavigationBar directly.
Solving this is possible but nontrivial, since the category method "replaces" the original method rather than subclassing it from the runtime's standpoint. This is why just using, say, super, won't work.
You should check out this post on "supersequent" implementation: http://cocoawithlove.com/2008/03/supersequent-implementation.html
(That link and some other related ideas are in the answer to this question: Using Super in an Objective C Category? )