125

I'm trying to convert my app to the Swift language.

I have this line of code:

[[UIBarButtonItem appearanceWhenContainedIn:[UINavigationBar class], nil]
                     setTitleTextAttributes:textDictionary
                                   forState:UIControlStateNormal];

How to convert it to Swift?

In Apple's docs, there is no such method.

Pang
  • 9,564
  • 146
  • 81
  • 122
AlexZd
  • 2,152
  • 2
  • 18
  • 29

11 Answers11

231

Update for iOS 9:

If you're targeting iOS 9+ (as of Xcode 7 b1), there is a new method in the UIAppearance protocol which does not use varargs:

static func appearanceWhenContainedInInstancesOfClasses(containerTypes: [AnyObject.Type]) -> Self

Which can be used like so:

UITextField.appearanceWhenContainedInInstancesOfClasses([MyViewController.self]).keyboardAppearance = .Light

If you still need to support iOS 8 or earlier, use the following original answer to this question.

For iOS 8 & 7:

These methods are not available to Swift because Obj-C varargs methods are not compatible with Swift (see http://www.openradar.me/17302764).

I wrote a non-variadic workaround which works in Swift (I repeated the same method for UIBarItem, which doesn't descend from UIView):

// UIAppearance+Swift.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIView (UIViewAppearance_Swift)
// appearanceWhenContainedIn: is not available in Swift. This fixes that.
+ (instancetype)my_appearanceWhenContainedIn:(Class<UIAppearanceContainer>)containerClass;
@end
NS_ASSUME_NONNULL_END

// UIAppearance+Swift.m
#import "UIAppearance+Swift.h"
@implementation UIView (UIViewAppearance_Swift)
+ (instancetype)my_appearanceWhenContainedIn:(Class<UIAppearanceContainer>)containerClass {
    return [self appearanceWhenContainedIn:containerClass, nil];
}
@end

Just be sure to #import "UIAppearance+Swift.h" in your bridging header.

Then, to call from Swift (for example):

# Swift 2.x:
UITextField.my_appearanceWhenContainedIn(MyViewController.self).keyboardAppearance = .Light

# Swift 3.x:
UITextField.my_appearanceWhenContained(in: MyViewController.self).keyboardAppearance = .light
Alex Pretzlav
  • 15,505
  • 9
  • 57
  • 55
32

ios 10 swift 3

UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).title = "Kapat"
Piotr Tomasik
  • 9,074
  • 4
  • 44
  • 57
14

For iOS 8 & 7:

I use a category based on Alex's answer to specify multiple containers. This is a workaround until Apple officially supports appearanceWhenContainedIn in Swift.

UIAppearance+Swift.h

@interface UIView (UIAppearance_Swift)
/// @param containers An array of Class<UIAppearanceContainer>
+ (instancetype)appearanceWhenContainedWithin: (NSArray *)containers;
@end

UIAppearance+Swift.m

@implementation UIView (UIAppearance_Swift)

+ (instancetype)appearanceWhenContainedWithin: (NSArray *)containers
{
    NSUInteger count = containers.count;
    NSAssert(count <= 10, @"The count of containers greater than 10 is not supported.");
    
    return [self appearanceWhenContainedIn:
            count > 0 ? containers[0] : nil,
            count > 1 ? containers[1] : nil,
            count > 2 ? containers[2] : nil,
            count > 3 ? containers[3] : nil,
            count > 4 ? containers[4] : nil,
            count > 5 ? containers[5] : nil,
            count > 6 ? containers[6] : nil,
            count > 7 ? containers[7] : nil,
            count > 8 ? containers[8] : nil,
            count > 9 ? containers[9] : nil,
            nil];
}
@end

Then add #import "UIAppearance+Swift.h" to your bridging header.

To use from Swift:

TextField.appearanceWhenContainedWithin([MyViewController.self, TableViewController.self]).keyboardAppearance = .Light

It was good if I could find a way using CVarArgType, but I found no clean solution.

Community
  • 1
  • 1
Yoichi Tagaya
  • 4,547
  • 2
  • 27
  • 38
  • Nice workaround to the broken-ness of varargs! It's really too bad there's no general solution other than (now) using Apple's iOS 9-only method. – Alex Pretzlav Jun 19 '15 at 23:55
12

Here's a less ugly, but still ugly, workaround inspired by @tdun.

  1. Create a class to hold your Objective-C appearance. For the purposes of this example, let's call it AppearanceBridger.
  2. Add this class to your bridging header. If you don't have a bridging header, create one.
  3. Create a class method in AppearanceBridger named +(void)setAppearance and put the Objective-C appearance code in this method. For example:


+ (void)setAppearance {
    [[UIView appearanceWhenContainedIn:[UITableViewHeaderFooterView class], nil] setBackgroundColor:[UIColor whiteColor]];
}
  1. In your Swift code where you set the appearance, call AppearanceBridger.setAppearance() and you should be good to go!

Hope this works well for people who see it.

Baub
  • 5,004
  • 14
  • 56
  • 99
4

Here's an ugly workaround solution I used....

Just make an Objective-C Cocoa Touch Class (UIViewController), named whatever you want.

I named mine WorkaroundViewController...

Now in (WorkaroundViewController.m):

-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

Run the Objective-C appearance code for .appearanceWhenContainedIn() (here's my example):

[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setDefaultTextAttributes:@{NSFontAttributeName: [UIFont fontWithName:@"Avenir-Light" size:16.0f]}];

Then create a bridging header for your Swift project and then initialize your Objective-C ViewController in your Swift code, like this (again, just my example):

var work : WorkaroundViewController = WorkaroundViewController()

Then you're done! Let me know if it works for you... Like I said, it's ugly, but works!

tcd
  • 1,585
  • 2
  • 17
  • 38
  • It is tmp solution, I think we should to find or to wait to some better way :) – AlexZd Sep 12 '14 at 08:39
  • @AlexZd, absolutely, I hope they include it in swift... But for now, if you need it, there ya go! – tcd Sep 12 '14 at 14:36
4

This can be extended to any class that conforms to the UIAppearance protocol -- not just UIViews. So here's a more generic version:

UIAppearance+Swift.h

#import <UIKit/UIKit.h>

@interface NSObject (UIAppearance_Swift)

+ (instancetype)appearanceWhenContainedWithin:(Class<UIAppearanceContainer>)containerClass;

@end

UIAppearance+Swift.m

#import "UIAppearance+Swift.h"

@implementation NSObject (UIAppearance_Swift)

+ (instancetype)appearanceWhenContainedWithin:(Class<UIAppearanceContainer>)containerClass {
    if ([self conformsToProtocol:@protocol(UIAppearance)]) {
        return [(id<UIAppearance>)self appearanceWhenContainedIn:containerClass, nil];
    }
    return nil;
}

@end
Eddie K
  • 503
  • 4
  • 18
3

I have created a repo for you guys who wanna use CocoaPods:

  1. Add this into your Podfile:

    pod 'UIViewAppearanceSwift'
    
  2. Import in your class:

    import UIViewAppearanceSwift
    
    func layout() {
        UINavigationBar.appearanceWhenContainedWithin(MFMailComposeViewController.self).barStyle = .Black
        UIBarButtonItem.appearanceWhenContainedWithin(UISearchBar.self).setTitleTextAttributes([NSFontAttributeName: UIFont.systemFontOfSize(15)], forState: UIControlState.Normal)
    }
    
  3. Reference: https://github.com/levantAJ/UIViewAppearanceSwift

Tai Le
  • 8,530
  • 5
  • 41
  • 34
1

Swift 4: iOS 9+

UIProgressView.appearance(whenContainedInInstancesOf: [LNPopupBar.self]).tintColor = .red
Yaroslav Dukal
  • 3,894
  • 29
  • 36
0

It seems Swift (at least as of Beta5) isn't able to support it for reasons unknown to me. Perhaps the language feature required is still in progress, as I can only assume they left it out of the interface for a good reason. Like you said, according to the docs it's still available in ObjC. Really disappointing.

hlfcoding
  • 2,532
  • 1
  • 21
  • 25
-3

You can use this:

UIBarButtonItem.appearance().setTitleTextAttributes(textDictionary, forState: UIControlState.Normal)

Edit: appearanceWhenContainedIn was removed in Swift. This answer was for the Beta 5 to change the appearance of the text of all bar buttons.

spinillos
  • 92
  • 4
-7

You should be able to just translate the Objective-C syntax into Swift syntax.

In swift the methods should be declared like this:

func appearanceWhenContainedIn(containerClass : <UIAppearanceContainer>)
func setTitleTextAttributes(_ attributes: NSDictionary!, forState state: UIControlState)

So you can try this:

UIBarButtonItem.appearanceWhenContainedIn(UINavigationBar).setTitleTextAttributes(textDictionary, forState: UIControlStateNormal)

I still have to figure out if this is the clean way to call a class method in Swift though.

Hope this helps,

Zedenem
  • 2,479
  • 21
  • 23
  • 1
    There is no this method appearanceWhenContainedIn in swift it isn't compile. Error:'UIBarButtonItem.Type' does not have a member named 'appearanceWhenContainedIn' – AlexZd Jun 14 '14 at 17:38
  • Hi @AlexZd, your comment is based on the actual XCode 6 Beta but if you look at the `UIAppearance` protocol documentation (to which `UIBarButtonItem` conforms), the `appearanceWhenContainedIn(_:) method exists (it's just not implemented in Swift yet): https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIAppearance_Protocol/index.html#//apple_ref/occ/intf/UIAppearance – Zedenem Jul 23 '14 at 08:38
  • @Zedenem I know, it's really dumb and you don't want to believe us - but this method literally cannot be called from Swift, check the documentation: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIAppearance_Protocol/index.html - the method is hidden from Swift – powerj1984 Oct 30 '14 at 17:35
  • Hi @powerj1984, it's not that I don't want to believe you. I was just hoping at the time I wrote my answer that this was just due to Swift's beta status. The fact that the method hasn't been deprecated but is just hidden from Swift is really strange, and having no explanation from Apple makes it even worst... But you're right, even if I don't understand why, my answer is wrong. Maybe is there a feature in Swift I am not thinking about that would allow us to do just that... – Zedenem Oct 30 '14 at 19:18
  • Haha, sorry - I really mean I don't want to believe me. It's just such a silly thing for Apple to screw up. It's weird as heck! – powerj1984 Oct 30 '14 at 19:22
  • I like your optimism thinking the issue might've been fixed before Swift was full on released though :) – powerj1984 Oct 30 '14 at 19:23