How to get object referece of UIPickerView when it

2019-06-26 02:30发布

I have a UIWebview contains a html "select" tag, which is shown as a dropdown list on the screen.

When I click the dropdown, the UIWebview brings up a UIWebSelectSinglePicker View automatically, which is shown as default OS pickerview.

I want to change the picker view background color and text color. How can I achieve this goal?

I tried to listen on UIKeyboardWillShowNotification event, but at that moment, this view has not been created.

Thanks in advance for any helps.

2条回答
来,给爷笑一个
2楼-- · 2019-06-26 03:04

I believe I've come up with an alternate solution to this problem. There are certain circumstances with the other solution proposed where the label colours appear incorrect (using the system default instead of the overridden colour). This happens while scrolling the list of items.

In order to prevent this from happening, we can make use of method swizzling to fix the label colours at their source (rather than patching them after they're already created).

The UIWebSelectSinglePicker is shown (as you've stated) which implements the UIPickerViewDelegate protocol. This protocol takes care of providing the NSAttributedString instances which are shown in the picker view via the - (NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component method. By swizzling the implementation with our own, we can override what the labels look like.

To do this, I defined a category on UIPickerView:

@implementation UIPickerView (LabelColourOverride)

- (NSAttributedString *)overridePickerView:(UIPickerView *)pickerView 
                     attributedTitleForRow:(NSInteger)row 
                              forComponent:(NSInteger)component
{
    // Get the original title
    NSMutableAttributedString* title = 
        (NSMutableAttributedString*)[self overridePickerView:pickerView 
                                       attributedTitleForRow:row 
                                                forComponent:component];

    // Modify any attributes you like. The following changes the text colour.
    [title setAttributes:@{NSForegroundColorAttributeName : [UIColor redColor]} 
                   range:NSMakeRange(0, title.length)];

    // You can also conveniently change the background of the picker as well. 
    // Multiple calls to set backgroundColor doesn't seem to slow the use of 
    // the picker, but you could just as easily do a check before setting the
    // colour to see if it's needed.
    pickerView.backgroundColor = [UIColor yellowColor];

    return title;
}

@end

Then using method swizzling (see this answer for reference) we swap the implementations:

[Swizzle swizzleClass:NSClassFromString(@"UIWebSelectSinglePicker") 
               method:@selector(pickerView:attributedTitleForRow:forComponent:) 
               forClass:[UIPickerView class] 
               method:@selector(overridePickerView:attributedTitleForRow:forComponent:)];

This is the Swizzle implementation I developed based off the link above.

@implementation Swizzle

+ (void)swizzleClass:(Class)originalClass 
              method:(SEL)originalSelector 
            forClass:(Class)overrideClass 
              method:(SEL)overrideSelector
{
    Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
    Method overrideMethod = class_getInstanceMethod(overrideClass, overrideSelector);
    if (class_addMethod(originalClass, 
                        originalSelector,
                        method_getImplementation(overrideMethod),
                        method_getTypeEncoding(overrideMethod))) {
        class_replaceMethod(originalClass,
                            overrideSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } 
    else {
        method_exchangeImplementations(originalMethod, overrideMethod);
    }
}

@end

The result of this is that when a label is requested, our override function is called, which calls the original function, which conveniently happens to return us a mutable NSAttributedString that we can modify in anyway we want. We could completely replace the return value if we wanted to and just keep the text. Find the list of attributes you can change here.

This solution allows you to globally change all the Picker views in the app with a single call removing the need to register notifications for every view controller where this code is needed (or defining a base class to do the same).

查看更多
祖国的老花朵
3楼-- · 2019-06-26 03:16

I managed to resolve the issue myself.

If someone also want to change the UIPickView on the fly, please take a look:

First, add a listener on UIKeyboardWillShowNotification event.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_pickerViewWillBeShown:) name:UIKeyboardWillShowNotification object:nil];

Second, when notification fired, call change background color method after delay. <-- This is very important, if call method without delay, the pickview does not exist at that moment.

- (void)_pickerViewWillBeShown:(NSNotification*)aNotification {
   [self performSelector:@selector(_resetPickerViewBackgroundAfterDelay) withObject:nil   afterDelay:0];
}

Third, go through the UIApplication windows and find out pickerView. And you can change what ever you want for pickerView.

-(void)_resetPickerViewBackgroundAfterDelay
{
  UIPickerView *pickerView = nil;
  for (UIWindow *uiWindow in [[UIApplication sharedApplication] windows]) {
    for (UIView *uiView in [uiWindow subviews]) {
      pickerView = [self _findPickerView:uiView];
    }
  }

  if (pickerView){
    [pickerView setBackgroundColor:UIColorFromRGB(0x00FF00)];
  }
}

(UIPickerView *) _findPickerView:(UIView *)uiView {
  if ([uiView isKindOfClass:[UIPickerView class]] ){
    return (UIPickerView*) uiView;
  }
  if ([uiView subviews].count > 0) {
    for (UIView *subview in [uiView subviews]){
      UIPickerView* view = [self _findPickerView:subview];
      if (view)
        return view;
    }
  }
  return nil;
}

Hope it will help.

查看更多
登录 后发表回答