Search code examples
iosswiftorientationlandscape-portraitdevice-orientation

Swift: Changing view/screen depending on device orientation. What is "efficiency wise" the better option?


Introduction

I'm creating a calendar app in which one of the screens has a landscape view and a portrait view. For simplicity picture the iOS apple calendar in which the landscape view is a week view (i.e. completely different than the portrait view).

Issue

I'm getting a sense of bad code structure and potential loss of efficiency in my current code. Since I basically use the users battery and CPU for the week view concurrently with the portrait view even though not everyone uses the week view. What is the better practice in implementing a different presentation depending on device rotation?

Question

  • Which pattern would be the more efficient approach? Is there a third option I haven't considered that would result in better performance?

My attempts

(I've also included a code example (below) that shows my implementation of these attempts in code.)

  1. Two UIViewControllers that is segued and "popped" depending on conditions of device orientation in viewWillTransition(). Although that became quickly out of hand since the method triggers in all view controller currently in memory/navigationStack, resulting in additional copies of viewControllers in the navigation stack if you swap between right landscape and left landscape.

  2. Using one UIViewController and two UIView subclass that is initialized and communicating to the view controller through the delegate-protocol pattern. In which during the viewWillTransition() I simply animate an alpha change between the two UIViews depending on the device orientation.

Code example

(I have provided two simplification to illustrate my attempts described above, methods such as dataSource and delegate methods for UICollectionViews are not included are not included in the example below.)

Attempt 1:

class PortraitCalendar: UIViewController {
    let portraitCalendarView : MonthCalendar = {
        // Setup of my portrait calendar, it is a UICollectionView subclass.
    }
    
     override func viewDidLoad() {
         super.viewDidLoad()
         view.addSubview(portraitCalendarView)
         // Additional setup..
     }
     
     override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
         super.viewWillTransition(to: size, with: coordinator)
         if UIDevice.current.orientation.isLandscape {
              performSegue(withIdentifier: "toLandscapeCalendar", sender: nil)
         } 
     }
 }

 class LandscapeCalendar: UIViewController {
     let landscapeView : LandscapeView = {
          // Setup of the landscape view, a UICollectionView subclass.
     }
     override func viewDidLoad() {
         super.viewDidLoad()
         view.addSubview(landscapeView)
         // Additional setup..
     }
     
     override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
         super.viewWillTransition(to: size, with: coordinator)
         if UIDevice.current.orientation.isPortrait {
              navigationController?.popViewController(animated: true)
         } 
     }
 }

Attempt 2:

class PortraitCalendar: UIViewController, LandscapeCalendarDelegate {
    let portraitCalendarView : MonthCalendar = {
        // Setup of my portrait calendar, it is a UICollectionView subclass.
    }

    // UIView subclass with a UICollectionView within it as a week calendar.
    let landscapeCalendar = LandscapeView() 

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(portraitCalendarView)
        view.addSubview(landscapeCalendar)
        landscapeCalendar.alpha = 0
        portraitCalendarView.alpha = 1
        // Constraints and additional setup as well of course.
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        if UIDevice.current.orientation.isLandscape {
            navigationController?.isToolbarHidden = true
            self.view.layoutIfNeeded()

            landscapeCalendarDelegate?.splitCalendarViewWillAppear()
            UIView.animate(withDuration: 0.1) {
                self.portraitCalendarView.alpha = 0
                self.landscapeCalendar.alpha = 1
            }
        } else {
            self.portraitCalendarView.alpha = 1
            self.landscapeCalendar.alpha = 0
        }
     }
}

Thanks for reading my question.


Solution

  • I'd definitely go for an option number 2.

    That way you encapsulate all the logic related to the calendar, for example for adding event or displaying it, in one view controller, without the need to reimplement the sane logic somewhere else (eg other view controller with landscape mode). Having two views for a different layout modes is not THAT easy to maintain, but if that's the only way to show the difference between the modes it really is a fine solution. And it's much easier to maintain than two view controllers with the very similar logic.