7

Users can switch theme in one section of app settings as showed below.

This is done by changing window.overrideUserInterfaceStyle.

While it works as expected, the change has no animation at all. The whole app goes to the selected style immediately as opposed to the smooth changes in iOS Settings app.

Simply place the assignment in the completion of UIView.animate(withDuration duration: TimeInterval, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) won't work.

app setting

francisfeng
  • 704
  • 1
  • 8
  • 22
  • what animation, what component and you're in theme setting so why not animating only the theme setting ? – Mohmmad S Aug 30 '20 at 07:25
  • Yes. But even only the setting table view controller seems too many. It's embedded in a navigation controller, which is embedded in a tab bar controller. – francisfeng Aug 30 '20 at 07:34
  • too much ? well you're only animating those few components you can try `UIApperiance` – Mohmmad S Aug 30 '20 at 07:40
  • Please see the comment I left in finebel's answer. I don't think `UIAppearance` can reduce much work. – francisfeng Aug 30 '20 at 08:37

5 Answers5

13

There is an easy way to change the app dark mode theme with animation

if let window = UIApplication.shared.keyWindow {
    UIView.transition (with: window, duration: 0.3, options: .transitionCrossDissolve, animations: {
        window.overrideUserInterfaceStyle = .dark //.light or .unspecified
    }, completion: nil)
}
Fadi Abuzant
  • 476
  • 8
  • 13
1

You could do something like the following:

var toggleVar = true

@IBAction func buttonDidTap(_ sender: UIButton) {
    
    UIView.animate(withDuration: 0.5, animations: {
        self.overrideUserInterfaceStyle = self.toggleVar ? .dark : .light
        self.view.backgroundColor = .systemBackground
    }) { (_) in
        //set overrideUserInterfaceStyle app wide
        UIApplication.shared.windows.first?.overrideUserInterfaceStyle = self.overrideUserInterfaceStyle
        self.toggleVar.toggle()
    }
}

So in order to animate the background change, you need to animate self.view.backgroundColor = .systemBackground. In the completion handler you can set the new overrideUserInterfaceStyle then for the entire app...

finebel
  • 2,227
  • 1
  • 9
  • 20
  • 1
    I got the idea. But is this the only way to go? Manually animating all current visible UIViews' colors seems too much to sustain. For example, I need to animate navigation bar, tableview, whose visible subviews includes a header view, text labels, separator views and accessory views. Also the tab bar. – francisfeng Aug 30 '20 at 06:36
  • I tried this but don't get the animation. I am using Eureka form framework but don't think its a problem with framework. Any ideas? https://github.com/xmartlabs/Eureka/issues/2219 – ashishn May 01 '22 at 13:36
0

I had the same problem. After fruitless attempts with UIView/UIWindow.animate/transition and animation options, I worked around the issue as follows:

  • Capture the whole screen
  • Add a fullscreen UIImageView with the captured image
  • Switch the user interface style in the background (which doesn't animate)
  • Fade out the UIImageView

Assuming you're also working with a UITableViewController within a UINavigationController, an example:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // Disable all checkmarks
    tableView.visibleCells.forEach { (cell) in
        cell.accessoryType = .none
    }
    
    // Set checkmark
    let cell = tableView.cellForRow(at: indexPath)
    cell?.accessoryType = .checkmark
    
    // Capture and add screenshot of current screen
    let imageView = UIImageView()
    imageView.image = UIApplication.shared.makeSnapshot()
    imageView.addTo(self.navigationController!.view).snp.makeConstraints { (maker) in
        maker.edges.equalToSuperview()
    }
    
    // Set new dark mode setting and fade out screenshot
    UIView.animate(withDuration: 0.5, animations: {
        // Important to set it within the animations closure, so that the status bar will animate
        UIApplication.shared.windows.first?.overrideUserInterfaceStyle = .dark
        imageView.alpha = 0
    }) { (_) in
        imageView.removeFromSuperview()
    }
    
    // Deselect row
    tableView.deselectRow(at: indexPath, animated: true)
}

Notes:

  • The makeSnapshot() method is from this post: https://stackoverflow.com/a/40953026/4489859
  • I use SnapKit to add/lay out views, so you probably need to change that line accordingly for your project
  • Change .dark to the correct dynamic value for your project

Result:

On the device itself, it's even smoother than the system's change animation for me!

Dharman
  • 30,962
  • 25
  • 85
  • 135
Flitskikker
  • 345
  • 2
  • 7
0

I use Xamarin and for me the solution is:

UIView TempView = Window.SnapshotView(true);    

Window.Add(TempView);

Window.OverrideUserInterfaceStyle = UIUserInterfaceStyle.Light;

UIView.Animate(0.5,
() => { 
    TempView.Alpha = 0;
},
() => {
    TempView.RemoveFromSuperview();
    TempView.Dispose();
});
Matteo
  • 39
  • 6
0

Swift 5

@objc func changeStyleHandler() {
    
    guard let window = UIApplication.shared.windows.first else { return }
    
    let newInterfaceStyle: UIUserInterfaceStyle = (traitCollection.userInterfaceStyle == .dark) ? .light : .dark
    
    UIView.transition (with: window, duration: 0.3, options: .transitionCrossDissolve, animations: {
        window.overrideUserInterfaceStyle = newInterfaceStyle
    }, completion: nil)
    
}
Pete Streem
  • 360
  • 5
  • 14