The ViewController in question is embedded in a UINavigationController
and presented as a .FormSheet
like so:
class PLViewController:UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.translucent = false
}
/// Embeds self into a UINavigationController, adds a "done" button to the navVC and uses the passed ViewController to present self embedded in the NavigationController.
/// - Parameters:
/// - presentingVC: ViewController which will present the formSheet.
/// - animated: If TRUE, the presentation of the formsheet will be animated.
func presentAsFormSheet (presentingVC:UIViewController, animated:Bool, completion:(() -> Void)?) {
let navVC = UINavigationController(rootViewController: self)
navVC.modalPresentationStyle = .FormSheet
let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Done, target: self, action: Selector("dismissFormSheet"))
doneButton.tintColor = GlobalVars.cautionColor
navigationItem.setLeftBarButtonItem(doneButton, animated: false)
presentingVC.presentViewController(navVC, animated: true, completion: completion)
}
/// Dismisses this ViewController with animation from a modal state.
func dismissFormSheet () {
dismissViewControllerAnimated(true, completion: nil)
}
}
When the VC is presented in the "floating" formsheet manner (non-fullscreen modal) it needs to essentially behave as is shown below:
Additionally, the layout needs to be manipulated further if the application is in either 1/3 split screen (but not 2/3's) or on iPhone in general. The iPhone part is easy enough to figure out, basically check the device type and respond accordingly in code.
Is there a to know that, when on iPad, a) in split-screen mode and b) using 1/3, 1/2 or 2/3?
Success! By subclassing UIViewController, I was able to create some methods to programmatically adjust the .FormSheet
modal to respond to the presentingVC's trait changes.
The UIViewController Subclass:
class MyViewControllerSubclass: UIViewController {
private func deviceOrientation () -> UIDeviceOrientation {
return UIDevice.currentDevice().orientation
}
private func getScreenSize () -> (description:String, size:CGRect) {
let size = UIScreen.mainScreen().bounds
let str = "SCREEN SIZE:\nwidth: \(size.width)\nheight: \(size.height)"
return (str, size)
}
private func getApplicationSize () -> (description:String, size:CGRect) {
let size = UIApplication.sharedApplication().windows[0].bounds
let str = "\n\nAPPLICATION SIZE:\nwidth: \(size.width)\nheight: \(size.height)"
return (str, size)
}
/// Called when the UIViewController's traitCollection has changed.
/// - Description:
/// Include any changes that need to be made when the trait collection changes.
func respondToSizeChange (layoutStyle:LayoutStyle) {
}
enum LayoutStyle: String {
case iPadFullscreen = "iPad Full Screen"
case iPadHalfScreen = "iPad 1/2 Screen"
case iPadTwoThirdScreen = "iPad 2/3 Screen"
case iPadOneThirdScreen = "iPad 1/3 Screen"
case iPhoneFullScreen = "iPhone"
}
/// - Returns: a `LayoutStyle` value containing information about what portion of the screen the application is consuming.
internal func determineLayout () -> LayoutStyle {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .iPhoneFullScreen
}
let screenSize = getScreenSize().size
let appSize = getApplicationSize().size
//let orientation = deviceOrientation()
let screenWidth = screenSize.width
let appWidth = appSize.width
if screenSize == appSize {
return .iPadFullscreen
}
if deviceOrientation() == .Portrait && screenSize != appSize {
return .iPadOneThirdScreen
}
// In case the math isn't exact, set a range.
let lowRange = screenWidth - 15
let highRange = screenWidth + 15
if lowRange / 2 <= appWidth && appWidth <= highRange / 2 {
return .iPadHalfScreen
} else if appWidth <= highRange / 3 {
return .iPadOneThirdScreen
} else {
return .iPadTwoThirdScreen
}
}
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
respondToSizeChange(determineLayout())
}
}
And on the ViewController that presents the modal:
class MainViewController:UIViewController {
/// The modally-presented formSheet.
var presentedFormSheet:MyViewControllerSubclass?
/// Modally presents the VC in `.FormSheet` style and sets the local `presentedFormSheet` variable to the VC.
func showContentRequirementVC() {
let vc = MyViewControllerSubclass(nibName: "myCustomViewController", bundle: nil)
vc.modalPresentationStyle = .FormSheet
presentedFormSheet = vc
presentViewController(vc, animated: true, completion: nil)
}
// When the trait collection changes, tell the presentedFormSheet about it.
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
presentedFormSheet?.respondToSizeChange(determineLayout())
}
}
Results: As shown in the below screenshots, the solid red bar is now only present when the formsheet is consuming the entire area of the app. There is enough information in the passed data that you can respond to full, 2/3, 1/2 and 1/3 individually as well. The modal will also respond to changes even after it has been presented.
Also demonstrated by the below screen shots is the reason this question is relevant. As you can see, the side-by-side tables work great until we get to 1/3 or iPhone. At this point, they need to be either on a tab-bar controller or use some other method so that only one table is shown at a time and the user can toggle between them. This particular VC is built with UIStackViews, so the behavior can easily be programmatically adjusted to suit the UI layout.