Formsheet iOS 8 Constraints Are Same as Iphones Constraints

Formsheet ios 8 constraints are same as iphones constraints

From the UIViewController class reference:

In a horizontally regular environment, a presentation style that displays the content centered in the screen. The width and height of the content area are smaller than the screen size and a dimming view is placed underneath the content. If the device is in a landscape orientation and the keyboard is visible, the position of the view is adjusted upward so that the view remains visible. All uncovered areas are dimmed to prevent the user from interacting with them.

In a horizontally compact environment, this option behaves the same as UIModalPresentationFullScreen.

Because the form sheet presentation on iPad is compact width and regular height, these are the values you'll get when you present a form sheet.


If you don't want the default size classes you can override them.

If your view controller is a child view controller of another, you can use setOverrideTraitCollection(_:forChildViewController:) and override the size class constraints for the child controller.

If your view controller is NOT a child view controller, you're not really supposed to change the trait collection, but you can do it using this hack.


The best solution would be to design your view controller to look appropriate in the default (correct) size constraints applied for a form sheet view controller presentation. You can usually do this by avoiding setting width constraints and only setting leading and trailing constraints.

Size classes on Modal Views

Because the form sheet presentation on iPad is compact width and regular height, It is taking those constraints.

Formsheet ios 8 constraints are same as iphones constraints

The solution is to override traitCollection in the Presented view controller

override var traitCollection: UITraitCollection
{
if UIDevice.isIPad()
{
let traits = UITraitCollection(horizontalSizeClass: UIUserInterfaceSizeClass.Regular)
let traits2 = UITraitCollection(verticalSizeClass: UIUserInterfaceSizeClass.Regular)
let traitCollection = UITraitCollection(traitsFromCollections: [traits, traits2])

return traitCollection
}
else
{
return super.traitCollection
}
}

Quick way to make all active constraints only apply to 1 size class

Please note - My answer pertains to the use of UIKit. I am unaware if it also applies to SwiftUI.

If you want to keep everything inside of the Storyboard then you're already doing the most that you can do; there is nothing else.

If you're open to doing some of the work programmatically; however, you can take a hybrid approach.

STEP 1:

To start, add UI objects to the storyboard & create IBOutlets as usual.

STEP 2:

Then, programmatically check the device's width & height.

STEP 3:

Then, programmatically set the sizes of each UI object's leading, trailing, top & bottom constraints.
Use the device's width & height that you acquired in step 2 & use them to determine what you want to set the constraints' constant to.

UITableViewCell with autolayout left margin different on iPhone and iPad

 How to fix it

After fighting with the apple bug reporting team with many sample projects and screenshots and dissecting that answer, I've found that the solution to have your custom-style cells behave consistently regarding their margins and be just like the default UITableViewCells, you have to do the following (mostly based on Becky's answer, I've highlighted what's different and what made it work for me) :

  1. Select your cell's content view in IB

  2. Go to the Size Inspector

  3. In the Layout Margins section, check Preserve Superview Margins (do not click the plus-sign)

    Checking preserve superview margins

  4. (And here's the key) Do the same for the cell itself (the content view's parent if you will)

    The cell and not the content view this time

  5. Setup your constraints as follows : Label.Leading = Superview.Leading Margin (with a constant of 0)

    Setting the constraint for the custom cell's label

Now all your cells will have their label consistent with the default cells! This works for me in Xcode 7 and up and it includes the fix mentioned in the thread I referred to. IB and the simulator should now show properly aligned labels.

Final result in the simulator

You could also do some of this programmatically, for example in the View Controller's class :

cell.preservesSuperviewLayoutMargins = true
cell.contentView.preservesSuperviewLayoutMargins = true

Or you could have it set up by calling UIAppearance once at startup (I only know Swift, sorry) :

UITableViewCell.appearance().preservesSuperviewLayoutMargins = true
UITableViewCell.appearance().contentView.preservesSuperviewLayoutMargins = true


How and why it works

As Ethan kindly pointed out, Apple's own documentation on UIView describes preservesSuperviewLayoutMargins as follows :

When the value of this property is true, the superview’s margins are also considered when laying out content. This margin affects layouts where the distance between the edge of a view and its superview is smaller than the corresponding margin. For example, you might have a content view whose frame precisely matches the bounds of its superview. When any of the superview’s margins is inside the area represented by the content view and its own margins, UIKit adjusts the content view’s layout to respect the superview’s margins. The amount of the adjustment is the smallest amount needed to ensure that content is also inside the superview’s margins.

Therefore, if you want your cell's content to align with the TableView's margins (it's great-grandparent if you will), you need to have your content's two ascendants, Content View and the Table Cell itself, preserve the margins of their own superview.

Why this isn't default behavior surprises me : I feel like most developers who don't want to customize everything would expect this "inheritance" by default.

How can I tell programmatically if a ViewController presented UIModalPresentationFormSheet opened on full screen or not?

The controller displays as a form sheet when the size class for the controller has regular width.

So on an iPhone 6+ in landscape, or on an iPad in any orientation the horizontal size class is regular and the form is displayed less than full screen width.

You can test for this in a controller using:

if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular) {
// ... Its showing as per the form specification
}
else{
// ... Its showing as a modal full screen.
}

Replace self with the variable for a controller if being called from somewhere else.

This also covers the case where you might use a popover, since when you use a popover on an iPad the size class changes to compact within the popover itself.

How to override trait collection for initial UIViewController? (with Storyboard)

Ok, I wish there was another way around this, but for now I just converted code from the Apple example to Swift and adjusted it to use with Storyboards.

It works, but I still believe it is an awful way to archive this goal.

My TraitOverride.swift:

import UIKit

class TraitOverride: UIViewController {

required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

var forcedTraitCollection: UITraitCollection? {
didSet {
updateForcedTraitCollection()
}
}

override func viewDidLoad() {
setForcedTraitForSize(view.bounds.size)
}

var viewController: UIViewController? {
willSet {
if let previousVC = viewController {
if newValue !== previousVC {
previousVC.willMoveToParentViewController(nil)
setOverrideTraitCollection(nil, forChildViewController: previousVC)
previousVC.view.removeFromSuperview()
previousVC.removeFromParentViewController()
}
}
}

didSet {
if let vc = viewController {
addChildViewController(vc)
view.addSubview(vc.view)
vc.didMoveToParentViewController(self)
updateForcedTraitCollection()
}
}
}

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator!) {
setForcedTraitForSize(size)
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
}

func setForcedTraitForSize (size: CGSize) {

let device = traitCollection.userInterfaceIdiom
var portrait: Bool {
if device == .Phone {
return size.width > 320
} else {
return size.width > 768
}
}

switch (device, portrait) {
case (.Phone, true):
forcedTraitCollection = UITraitCollection(horizontalSizeClass: .Regular)
case (.Pad, false):
forcedTraitCollection = UITraitCollection(horizontalSizeClass: .Compact)
default:
forcedTraitCollection = nil
}
}

func updateForcedTraitCollection() {
if let vc = viewController {
setOverrideTraitCollection(self.forcedTraitCollection, forChildViewController: vc)
}
}

override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
performSegueWithIdentifier("toSplitVC", sender: self)
}

override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if segue.identifier == "toSplitVC" {
let destinationVC = segue.destinationViewController as UIViewController
viewController = destinationVC
}
}

override func shouldAutomaticallyForwardAppearanceMethods() -> Bool {
return true
}

override func shouldAutomaticallyForwardRotationMethods() -> Bool {
return true
}
}

To make it work you need to add a new UIViewController on the storyboard and made it the initial. Add show segue from it to your real controller like this:
storyboard

You need to name the segue "toSplitVC":
segue name

and set initial controller to be TraitOverride:
assign controller

Now it should work for you too. Let me know if you find a better way or any flaws in this one.

Resize tableview in form sheet on iPad when keyboard is shown

Okay, I stumbled on this issue myself. I created a solution that works in every situation (so not only for viewControllers presented as a form sheet). Solution is in Swift 3, so you still need to convert it to Objective-C, but that shouldn't be a problem if your read the comments carefully.

The key of the solution is to update the tableView (or scrollview) insets when the keyboard animation is finished, and the form sheet is on it's new position.

In your UIViewController subclass add:

override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: .UIKeyboardWillChangeFrame, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: .UIKeyboardWillHide, object: nil)
}

This will add an observer in case the keyboard will show / hide. We also need to unsubscribe from these notifications, or the app will crash:

deinit {
NotificationCenter.default.removeObserver(self)
}

And finally, the most important code:

func getTableViewInsets(keyboardHeight: CGFloat) -> UIEdgeInsets {
// Calculate the offset of our tableView in the
// coordinate space of of our window
let window = (UIApplication.shared.delegate as! AppDelegate).window!
let tableViewFrame = tableView.superview!.convert(tableView.frame, to: window)

// BottomInset = part of keyboard that is covering the tableView
let bottomInset = keyboardHeight
- ( window.frame.height - tableViewFrame.height - tableViewFrame.origin.y )

// Return the new insets + update this if you have custom insets
return UIEdgeInsetsMake(
tableView.contentInset.top,
tableView.contentInset.left,
bottomInset,
tableView.contentInset.right
)
}

func keyboardWillChangeFrame(_ notification: Notification){
guard let info = (notification as NSNotification).userInfo else {
return
}

guard let animationDuration = info[UIKeyboardAnimationDurationUserInfoKey] as? TimeInterval else {
return
}

// Default: keyboard will hide:
var keyboardHeight: CGFloat = 0

if notification.name == .UIKeyboardWillChangeFrame {
// The keyboard will show
guard let keyboardFrame = info[UIKeyboardFrameEndUserInfoKey] as? NSValue else {
return
}

keyboardHeight = keyboardFrame.cgRectValue.height
}

let contentInsets = getTableViewInsets(keyboardHeight: keyboardHeight)

UIView.animate(withDuration: animationDuration, animations: {
self.tableView.contentInset = contentInsets
self.tableView.scrollIndicatorInsets = contentInsets
}, completion: {(completed: Bool) -> Void in
// Chances are the position of our view has changed, (form sheet)
// so we need to double check our insets
let contentInsets = self.getTableViewInsets(keyboardHeight: keyboardHeight)
self.tableView.contentInset = contentInsets
self.tableView.scrollIndicatorInsets = contentInsets
})
}


Related Topics



Leave a reply



Submit