Search code examples
iosswiftuiimagepickercontrolleruiappearancetintcolor

Change TintColor of UIImagePickerController


I've set a global tintColor in my app by calling:

UIView.appearance().tintColor = myColor

However, the same color applies to the UIImagePickerController and only some of the UI elements are colored: For example, the sun icon has a custom tintColor while the focus frame has the default one.

How is it possible not to apply a global appearance configuration to the UIImagePickerController?

enter image description here

My proposed solution doesn't fully address the issue, since the inputAccessoryView still has its own tintColor:

window?.tintColor = myColor

Update:

I configure the application-wide tintcolor on launch:

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    configureWindowAndInitialViewController()
    UIView.appearance().tintColor = .cyan // Setting a global tintColor
    return true
  }

Then, I show an UIImagePickerController:

  func presentPhotoPicker(sourceType: UIImagePickerController.SourceType) {
    if let mediaTypes = UIImagePickerController.availableMediaTypes(for: sourceType)?
      .filter({$0 == kUTTypeImage as String}),
      !mediaTypes.isEmpty {
      let picker = UIImagePickerController()
      picker.delegate = self
      picker.mediaTypes = mediaTypes
      picker.sourceType = sourceType
      picker.allowsEditing = true
      controller?.present(picker, animated: true, completion: nil)
    }
  }

Input accessory view:

To add an input accessory view, first of all, a textField should be created. Then it is possible to just attach a toolbar to the Textfield:

let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: sefl.view, action: #selector(UIView.endEditing))
items.append(contentsOf: [spacer, doneButton])

toolbar.setItems(items, animated: false)
textField.inputAccessoryView = toolbar

enter image description here


Solution

  • After you change window?.tintColor, inputAccessoryView.tintColor isn't changed because inputAccessoryView isn't a subview of current window (they don't have same view’s hierarchy) so window.tintColor won't affect inputAccessoryView.

    To resolve problem, I suggest to use NotificationCenter to observe and change window.tintColor each time a new UIWindow becomes visible. You can put the code inside AppDelegate

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
      // Override point for customization after application launch.
      NotificationCenter.default.addObserver(self, selector: #selector(self.windowDidBecomeVisible), name: .UIWindowDidBecomeVisible, object: nil)
      return true
    }
    
    @objc func windowDidBecomeVisible(notification: NSNotification) {
      // Each time an `UIWindow` becomes visible, change its |tintColor|
      let visibleWindow = notification.object as! UIWindow
      visibleWindow.tintColor = .red
    }
    

    If you want to change tintColor only for the UIImagePickerController, you need to change tintColor for every view which is displayed on UIImagePickerController.

    1. Define an extension to change all subview's tintColor.

      extension UIView {
        func changeTintColor(color: UIColor) -> Void {
          for view in subviews{
            view.tintColor = color
            view.changeTintColor(color: color);
          }
        }
      }
      
    2. Use UINavigationControllerDelegate to get UIViewController which is displayed on UIImagePickerView. Change view's tintColor inside this controller

      func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
        viewController.view.changeTintColor(color: .red)
      }
      
    3. Don't forget to set UIImagePickerController's delegate to self.