How to determine the Today extension left margin properly in iOS 8? - ios

I'm trying to find out how to calculate the left margin in the Today extension main view to align the contents to the rest of the Today view labels.
Here's an example with a clean Xcode project using a Today extensions (I've added color to the view backgrounds and drawn a dashed red line to illustrate where I'd like to align the Hello World UILabel).
The result in iPhone 6 Plus simulator (left side landscape, right side portrait) can be found from the image below:
In the image, notice that the green main view left boundary is placed differently related to the app name UILabel "testi2". It also seems that the red line - main views left border alignment is different in each device: iPhone 5x, iPhone 6 and iPads.
The behavior can be reproduced using a clean Xcode project (I'm using Xcode 6.1.1, iOS 8.1 and Swift):
Create an empty Xcode project (A single-view application)
Add a new Target: Extensions > Today extension
From the Today extension group, find MainInterface.storyboard and make the main view background green and Hello world UILabel background red:
How do I align the the Hello World UILabel (red background) to the dashed line? Or how do I align the main view (green background) to the dashed line?

Did you try this one ?
Objective-C:
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets
{
return UIEdgeInsetsZero;
}
Swift:
func widgetMarginInsetsForProposedMarginInsets(defaultMarginInsets: UIEdgeInsets) -> UIEdgeInsets {
return UIEdgeInsetsZero
}
Otherwise it looks like you will have to set them manually according to this So-Thread:
EDIT:
Looks like this method is deprecated and is not called for devices running >= iOS10. However I could not find any documentation on an alternative. If you have any information on this please add to this post so everyone can profit. Just make sure when using this function that it will not be called on >= iOS10.
Source: Apple Documentation

I tried using the left value of the defaultMarginInset of -widgetMarginInsetsForProposedMarginInsets with mixed results: On the iPhone 5S screen dimensions, it can be used to get the same inset as the default calendar widget of iOS (note that the right margin of the time label aligns with the blue line):
On the iPhone 6, you get similar results, i.e. it also aligns. However, on iPhone 6 Plus, the calendar widget somehow scales the inset:
Note that in the landscape version, neither the time nor the lines align to anything.
In conclusion, I would say that you can safely use defaultMarginInset.left to get decent results.
Swift code:
class TodayViewController: UIViewController, NCWidgetProviding {
var defaultLeftInset: CGFloat = 0
var marginIndicator = UIView()
override func viewDidLoad() {
super.viewDidLoad()
marginIndicator.backgroundColor = UIColor.whiteColor()
view.addSubview(marginIndicator)
}
func widgetMarginInsetsForProposedMarginInsets(var defaultMarginInsets: UIEdgeInsets) -> UIEdgeInsets {
defaultLeftInset = defaultMarginInsets.left
defaultMarginInsets.left = 0
return defaultMarginInsets
}
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) {
marginIndicator.frame = CGRectMake(defaultLeftInset, 0, 10, view.frame.size.height)
completionHandler(NCUpdateResult.NewData)
}
}

My temporary solution goes as follows. I'm using constant values for setting the left and top margins. This way I can align the content exactly like it's, for example, in Tomorrow Summary widget.
First some helper methods for determining the device type (adapted from this answer):
struct ScreenSize {
static let SCREEN_WIDTH = UIScreen.mainScreen().bounds.size.width
static let SCREEN_HEIGHT = UIScreen.mainScreen().bounds.size.height
static let SCREEN_MAX_LENGTH = max(ScreenSize.SCREEN_WIDTH,
ScreenSize.SCREEN_HEIGHT)
static let SCREEN_MIN_LENGTH = min(ScreenSize.SCREEN_WIDTH,
ScreenSize.SCREEN_HEIGHT)
}
struct DeviceType {
static let iPhone4 = UIDevice.currentDevice().userInterfaceIdiom == .Phone
&& ScreenSize.SCREEN_MAX_LENGTH < 568.0
static let iPhone5 = UIDevice.currentDevice().userInterfaceIdiom == .Phone
&& ScreenSize.SCREEN_MAX_LENGTH == 568.0
static let iPhone6 = UIDevice.currentDevice().userInterfaceIdiom == .Phone
&& ScreenSize.SCREEN_MAX_LENGTH == 667.0
static let iPhone6Plus = UIDevice.currentDevice().userInterfaceIdiom == .Phone
&& ScreenSize.SCREEN_MAX_LENGTH == 736.0
static let iPad = UIDevice.currentDevice().userInterfaceIdiom == .Pad
}
Then using widgetMarginInsetsForProposedMarginInsets: as suggested I overwrite the left and top insets as follows:
func widgetMarginInsetsForProposedMarginInsets(defaultMarginInsets: UIEdgeInsets)
-> UIEdgeInsets {
var insets = defaultMarginInsets
let isPortrait = UIScreen.mainScreen().bounds.size.width
< UIScreen.mainScreen().bounds.size.height
insets.top = 10.0
if DeviceType.iPhone6Plus {
insets.left = isPortrait ? 53.0 : 82.0
} else if DeviceType.iPhone6 {
insets.left = 49.0
} else if DeviceType.iPhone5 {
insets.left = 49.0
} else if DeviceType.iPhone4 {
insets.left = 49.0
} else if DeviceType.iPad {
insets.left = isPortrait ? 58.0 : 58.0
}
return insets
}
However, this is not the solution I'm looking for - I'd like to get rid of hardcoding per-device-per-orientation pixel values.

Related

How do I scale a UIView designed for iPad, for use with an iPhone?

I built an iPad game and am now trying to port it to the iPhone. One of my interfaces in the game has buttons that are laid in specific places on top of an image, so I'd like to simply scale what I have built for the iPad down to iPhone size.
Thus far, I have had a little success simply defining my buttons in terms of positions and sizes on the iPad, then simply scaling each button proportionally in code.
//
// ExerciseMenuView.swift
// Reading Expressway
//
// Created by Montana Burr on 7/29/16.
// Copyright © 2016 Montana. All rights reserved.
//
import UIKit
import QuartzCore
#IBDesignable #objc class ExerciseMenuView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
// Adjust subviews according to device screen size.
let iPadProWidth = CGFloat(1024);
let iPadProHeight = CGFloat(768);
let deviceWidth = UIScreen.main.bounds.width;
let deviceHeight = UIScreen.main.bounds.height;
for subview in self.subviews {
let subviewWidthMultiplier = subview.frame.width / iPadProWidth;
let subviewHeightMultiplier = subview.frame.height / iPadProHeight;
let subviewCenterXMultiplier = subview.frame.origin.x / iPadProWidth;
let subviewCenterYMultiplier = subview.frame.origin.y / iPadProHeight;
let subviewHeight = subviewHeightMultiplier * deviceHeight;
let subviewWidth = deviceWidth * subviewWidthMultiplier;
let subviewX = subviewCenterXMultiplier * deviceWidth;
let subviewY = subviewCenterYMultiplier * deviceHeight;
subview.frame = CGRect.init(x: subviewX, y: subviewY, width: subviewWidth, height: subviewHeight)
}
}
}
This is roughly what the screen looks like now, on iPad:
This is what I want it to look like on an iPhone 8:
This is what it actually looks like on an iPhone 8 simulator:
I think layoutSubviews called 2 or more time so your result very small.
You can change frame in init method.
OR add flag for check is this first calling
var isFirstTime = true
override func layoutSubviews() {
super.layoutSubviews()
if isFirstTime {
isFirstTime = false
// Adjust subviews according to device screen size.
...
}
}

How to know whether the viewcenter conincide with other view center?

I have a scroll view and a subview(which is UIView) and am just wanted to know whether the view is in center of the scrollview. I was just trying to compare the view.center for both but which are not equal. Please let me know if there are any other possible approach.
Try following comparison(Xcode 9.3 with Swift4.1):
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let centerSubView = scrollView.convert(subView.center, to: scrollView.superview)
//Convert CGFloat to Int to avoid decimal problem before comparison
if Int(centerSubView.x) == Int(scrollView.center.x) && Int(centerSubView.y) == Int(scrollView.center.y) {
print("equal center")
}
}

Detect if app is running in Slide Over or Split View mode in iOS 9

In iOS 9, is it possible to detect when an app is running in iOS 9's Slide Over or Split View mode?
I've tried reading through Apple's documentation on iOS 9 multitasking, but haven't had any luck with this…
I ask because I might have a feature in my app that I'd like to disable when the app is opened in a Slide Over.
Just check if your window occupies the whole screen:
BOOL isRunningInFullScreen = CGRectEqualToRect([UIApplication sharedApplication].delegate.window.frame, [UIApplication sharedApplication].delegate.window.screen.bounds);
If this is false, then you're running in a split view or a slide over.
Here is the code snipped which will automatically maintain this flag irrespective of rotation
-(void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
// simply create a property of 'BOOL' type
isRunningInFullScreen = CGRectEqualToRect([UIApplication sharedApplication].delegate.window.frame, [UIApplication sharedApplication].delegate.window.screen.bounds);
}
Just another way to repackage all of this
extension UIApplication {
public var isSplitOrSlideOver: Bool {
guard let w = self.delegate?.window, let window = w else { return false }
return !window.frame.equalTo(window.screen.bounds)
}
}
then you can just
UIApplication.shared.isSplitOrSlideOver in Swift
UIApplication.sharedApplication.isSplitOrSlideOver in Objective-C
Note that, in Swift, the window object is a double optional... WTF!
For iOS 13+ (note, I haven't tested the iOS 13 code myself yet)
extension UIApplication {
public var isSplitOrSlideOver: Bool {
guard let window = self.windows.filter({ $0.isKeyWindow }).first else { return false }
return !(window.frame.width == window.screen.bounds.width)
}
}
I'm late to the party, but if you want a property that works independent of the orientation, try this one:
extension UIApplication
{
func isRunningInFullScreen() -> Bool
{
if let w = self.keyWindow
{
let maxScreenSize = max(UIScreen.mainScreen().bounds.size.width, UIScreen.mainScreen().bounds.size.height)
let minScreenSize = min(UIScreen.mainScreen().bounds.size.width, UIScreen.mainScreen().bounds.size.height)
let maxAppSize = max(w.bounds.size.width, w.bounds.size.height)
let minAppSize = min(w.bounds.size.width, w.bounds.size.height)
return maxScreenSize == maxAppSize && minScreenSize == minAppSize
}
return true
}
}
Like the solution by Dan Rosenstark, but changed to work on the new iPad Pro's that seem to report a different frame and screen.bounds height based on if it's ran directly on the device through Xcode, or if it is compiled and released through TestFlight or App Store. The height would return 980 when through AS or TF, rather than 1024 as it was supposed to like through Xcode causing it to be impossible to return true.
extension UIApplication {
public var isSplitOrSlideOver: Bool {
guard let w = self.delegate?.window, let window = w else { return false }
return !(window.frame.width == window.screen.bounds.width)
}
}
I recently had to determine display style of an application based including, not only if it changed to split view or slide-over, but also what portion of the screen was being utilized for the application (full, 1/3, 1/2, 2/3). Adding this to a ViewController subclass was able to solve the issue.
/// Dismisses this ViewController with animation from a modal state.
func dismissFormSheet () {
dismissViewControllerAnimated(true, completion: nil)
}
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)
}
func respondToSizeChange (layoutStyle:LayoutStyle) {
// Respond accordingly to the change in size.
}
enum LayoutStyle: String {
case iPadFullscreen = "iPad Full Screen"
case iPadHalfScreen = "iPad 1/2 Screen"
case iPadTwoThirdScreeen = "iPad 2/3 Screen"
case iPadOneThirdScreen = "iPad 1/3 Screen"
case iPhoneFullScreen = "iPhone"
}
private func determineLayout () -> LayoutStyle {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .iPhoneFullScreen
}
let screenSize = getScreenSize().size
let appSize = getApplicationSize().size
let screenWidth = screenSize.width
let appWidth = appSize.width
if screenSize == appSize {
return .iPadFullscreen
}
// Set a range in case there is some mathematical inconsistency or other outside influence that results in the application width being less than exactly 1/3, 1/2 or 2/3.
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 .iPadTwoThirdScreeen
}
}
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
respondToSizeChange(determineLayout())
}
Here is a simpler and less fragile way with no constants, that I use in an iPhone/iPad iOS app.
This code also distinguishes between slide over and split view.
I'm returning String values here for clarity, feel free to use enum values and to merge the two cases of fullscreen as suits your app.
func windowMode() -> String {
let screenRect = UIScreen.main.bounds
let appRect = UIApplication.shared.windows[0].bounds
if (UIDevice.current.userInterfaceIdiom == .phone) {
return "iPhone fullscreen"
} else if (screenRect == appRect) {
return "iPad fullscreen"
} else if (appRect.size.height < screenRect.size.height) {
return "iPad slide over"
} else {
return "iPad split view"
}
}
You can watch both -willTransitionToTraitCollection:withTransitionCoordinator: for the size class and viewWillTransitionToSize:withTransitionCoordinator: for the CGSize of your view. Hardcoding in size values isn't recommended though.
The horizontal size class will be compact when in slide over or 33% split view. I don't think you can detect once you go to 50% or 66% though.
I made an edit to #Michael Voccola solution which fixed the problem for orientation
I used this way in my situation to detect all iPad split screen state and handling layout
Just call determineLayout() to get current layoutStyle
private func getScreenSize() -> CGRect {
let size = UIScreen.main.bounds
return size
}
private func getApplicationSize() -> CGRect {
let size = UIApplication.shared.windows[0].bounds
return size
}
enum LayoutStyle: String {
case iPadFullscreen = "iPad Full Screen"
case iPadHalfScreen = "iPad 1/2 Screen"
case iPadTwoThirdScreeen = "iPad 2/3 Screen"
case iPadOneThirdScreen = "iPad 1/3 Screen"
case iPhoneFullScreen = "iPhone"
}
func determineLayout() -> LayoutStyle {
if UIDevice.current.userInterfaceIdiom == .phone {
return .iPhoneFullScreen
}
let screenSize = getScreenSize().size
let appSize = getApplicationSize().size
let screenWidth = screenSize.width
let appWidth = appSize.width
if screenSize == appSize {
// full screen
return .iPadFullscreen
}
let persent = CGFloat(appWidth / screenWidth) * 100.0
if persent <= 55.0 && persent >= 45.0 {
// The view persent between 45-55 that's mean it's half screen
return .iPadHalfScreen
} else if persent > 55.0 {
// more than 55% that's mean it's 2/3
return .iPadTwoThirdScreeen
} else {
// less than 45% it's 1/3
return .iPadOneThirdScreen
}
}
extension UIApplication {
func isRunningInFullScreen() -> Bool {
if let w = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.compactMap({$0 as? UIWindowScene})
.first?.windows
.filter({$0.isKeyWindow}).first {
let maxScreenSize = max(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height)
let minScreenSize = min(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height)
let maxAppSize = max(w.bounds.size.width, w.bounds.size.height)
let minAppSize = min(w.bounds.size.width, w.bounds.size.height)
return maxScreenSize == maxAppSize && minScreenSize == minAppSize
}
return true
}
}
here is the code for those who don't want to see swift lint complaints for deprecated keyWindow
After much 'tinkering', I have found a solution for my App that may work for you:
In AppDelegate.swift, create the following variable:
var slideOverActive: Bool = false
Then, in ALL of your view controllers, add the UIApplicationDelegate to the Class definition, create an appDelegate variable, and then add the below traitCollectionDidChange function:
class myViewController: UIViewController, UIApplicationDelegate {
var appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
let screenWidth = UIScreen.mainScreen().bounds.width
if previousTraitCollection != nil {
let horizontalSizeClass: Int = previousTraitCollection!.horizontalSizeClass.rawValue
if screenWidth == 1024 || screenWidth == 768 { // iPad
if horizontalSizeClass == 2 { // Slide Over is ACTIVE!
appDelegate.slideOverActive = true
} else {
appDelegate.slideOverActive = false
}
}
}
}
}
Then, wherever in your code you wish to check whether the slide-over is active or not, simply check:
if appDelegate.slideOverActive == true {
// DO THIS
} else {
// DO THIS
}
It's a bit of a workaround, but it works for me at the moment.
Happy trails!
Adding to #Tamas's answer:
Here is the code snippet that will automatically maintain this flag irrespective of rotation.
-(void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
// simply create a property of 'BOOL' type
isRunningInFullScreen = CGRectEqualToRect([UIApplication sharedApplication].delegate.window.frame, [UIApplication sharedApplication].delegate.window.screen.bounds);
}
Trying [UIScreen mainScreen].bounds,
self.window.screen.bounds,
self.window.frame,
UIApplication.sharedApplication.keyWindow.frame and so on, the only working solution was deprecated method
CGRect frame = [UIScreen mainScreen].applicationFrame;
Which I fixed this way
CGRect frame = [UIScreen mainScreen].applicationFrame;
frame = CGRectMake(0, 0, frame.size.width + frame.origin.x, frame.size.height + frame.origin.y);
self.window.frame = frame;
And I'm really late to the party! But nonetheless, here's a simple, swifty solution to the problem. Using let width = UIScreen.mainScreen().applicationFrame.size.width we can detect the width of my app's window, and then have things occur when it is smaller than a certain number (i.e. on iPhone screen or in split view), useful to make different things happen on smaller screens. To have the computer check the width over and over again, we can run an NSTimer every hundredth of a second, then do stuff if the width is higher/lower than something.
Some measurements for you (you have to decide what width to make stuff occur above/below):
iPhone 6S Plus: 414.0mm
iPhone 6S: 375.0mm
iPhone 5S: 320.0mm
iPad (portrait): 768.0mm
iPad (1/3 split view): 320.0mm
iPad Air 2 (1/2 split view): 507.0mm
iPad (landscape): 1024.0mm
Here's a code snippet:
class ViewController: UIViewController {
var widthtimer = NSTimer()
func checkwidth() {
var width = UIScreen.mainScreen().applicationFrame.size.width
if width < 507 { // The code inside this if statement will occur if the width is below 507.0mm (on portrait iPhones and in iPad 1/3 split view only). Use the measurements provided in the Stack Overflow answer above to determine at what width to have this occur.
// do the thing that happens in split view
textlabel.hidden = false
} else if width > 506 {
// undo the thing that happens in split view when return to full-screen
textlabel.hidden = true
}
}
override func viewDidAppear(animated: Bool) {
widthtimer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "checkwidth", userInfo: nil, repeats: true)
// runs every hundredth of a second to call the checkwidth function, to check the width of the window.
}
override func viewDidDisappear(animated: Bool) {
widthtimer.invalidate()
}
}
I hope this can help anyone who comes peeking!
By using below code you can check splitViewController is Collapsed or Not
if splitViewController?.isCollapsed ?? false {
// splitview collapsed
} else {
// splitview not collapsed
}

iPad Landscape and Portrait different layouts with Size Class

How to design iPad Landscape and Portrait screens with different Layouts using Size class.
I could find only w-regular and h-regular for both orientations. Example: I need to align 2 views vertically in portrait and horizontally in landscape using Size Class
Finally I found a solution :
if traitCollection.verticalSizeClass == .Regular && traitCollection.horizontalSizeClass == .Regular {
var orientation:UIInterfaceOrientation = UIApplication.sharedApplication().statusBarOrientation;
if orientation == UIInterfaceOrientation.LandscapeLeft || orientation == UIInterfaceOrientation.LandscapeRight {
// orientation is landscape
} else {
// orientation is portrait
}
}
It appears to be Apple's intent to treat both iPad orientations as the same -- but as a number of us are finding, there are very legitimate design reasons to want to vary the UI layout for iPad Portrait vs. iPad Landscape.
However, please see this answer for another approach to adapting size classes to do what we need:
https://stackoverflow.com/a/28268200/4517929
For Swift 3 it would look like this:
override func overrideTraitCollection(forChildViewController childViewController: UIViewController) -> UITraitCollection? {
if UI_USER_INTERFACE_IDIOM() == .pad &&
view.bounds.width > view.bounds.height {
let collections = [UITraitCollection(horizontalSizeClass: .regular),
UITraitCollection(verticalSizeClass: .compact)]
return UITraitCollection(traitsFrom: collections)
}
return super.overrideTraitCollection(forChildViewController: childViewController)
}
It will use wRhC instead of wRhR for iPad devices in landscape mode.
Put this code to your base view controller, i.e. this rule will work for all controllers that were presented by this one.
You can put any additional conditions here... For example, if you want this rule to be working only for specific view controller, your if operator would look like this:
if UI_USER_INTERFACE_IDIOM() == .pad &&
childViewController is YourSpecificViewController &&
view.bounds.width > view.bounds.height {
let collections = [UITraitCollection(horizontalSizeClass: .regular),
UITraitCollection(verticalSizeClass: .compact)]
return UITraitCollection(traitsFrom: collections)
}
Swift 4
override func overrideTraitCollection(forChildViewController childViewController: UIViewController) -> UITraitCollection? {
if UIDevice.current.userInterfaceIdiom == .pad && UIDevice.current.orientation.isLandscape {
return UITraitCollection(traitsFrom:[UITraitCollection(verticalSizeClass: .compact), UITraitCollection(horizontalSizeClass: .regular)])
}
return super.overrideTraitCollection(forChildViewController: childViewController)
}
I like to create a custom subclass of navigationController and then set a storyboards initial Navigation controller to that class. You can also do something similar with a ViewController.
Example:
import UIKit
class NavigationControllerWithTraitOverride: UINavigationController {
// If you make a navigationController a member of this class the descendentVCs of that navigationController will have their trait collection overridden with compact vertical size class if the user is on an iPad and the device is horizontal.
override func overrideTraitCollection(forChildViewController childViewController: UIViewController) -> UITraitCollection? {
if UIDevice.current.userInterfaceIdiom == .pad && UIDevice.current.orientation.isLandscape {
return UITraitCollection(traitsFrom:[UITraitCollection(verticalSizeClass: .compact), UITraitCollection(horizontalSizeClass: .regular)])
}
return super.overrideTraitCollection(forChildViewController: childViewController)
}
}
Note: You should not override traitCollection as per the docs
Important
Use the traitCollection property directly. Do not override it. Do not provide a custom implementation.

Landscape vs. Portrait background image in Asset Catalog

I have a use case that seems to fall into the cracks between asset catalogs and size classes. My Universal app needs a full screen background image that is different in Landscape and Portrait orientations, and Landscape is only supported for the iPad & iPhone 6.
Since I can't add landscape images to the asset catalog for a new image set, my current solution looks like this (supports iOS 7 & 8):
// permit landscape mode only for iPads & iPhone 6 (would prefer to use size classes, but...?)
override func shouldAutorotate() -> Bool {
let size = view.frame.size
let maxv = max(size.width, size.height)
return ((maxv > 700.0) || (maxv == 512.0)) ? true : false
}
// this will be triggered only for iOS 7, as long as viewWillTransitionToSize:withTransitionCoordinator: is also implemented!
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
adjustToOrientation(toInterfaceOrientation)
}
// this will be triggered only for iOS 8
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
let orientation = UIApplication.sharedApplication().statusBarOrientation
// need to reverse the sense of the orientation here; Application still has the previous orientation
switch orientation {
case .Portrait, .PortraitUpsideDown:
adjustToOrientation(.LandscapeLeft)
case .LandscapeLeft, .LandscapeRight:
adjustToOrientation(.Portrait)
default:
adjustToOrientation(.Portrait)
}
}
func adjustToOrientation(newInterfaceOrientation: UIInterfaceOrientation) {
let size = view.frame.size
// rotation already permitted only for iPad & iPhone 6, but need to know which one (size classes useless here?)
switch (max(size.width, size.height)) {
case 1024.0:
if UIInterfaceOrientationIsLandscape(newInterfaceOrientation) {
backgroundImage.image = UIImage(named: "Background-Landscape#2x~ipad.png")
} else {
backgroundImage.image = UIImage(named: "Background#2x~ipad.png")
}
case 736.0:
if UIInterfaceOrientationIsLandscape(newInterfaceOrientation) {
backgroundImage.image = UIImage(named: "Background-Landscape#3x~iphone.png")
} else {
backgroundImage.image = UIImage(named: "Background#3x~iphone.png")
}
case 512.0:
if UIInterfaceOrientationIsLandscape(newInterfaceOrientation) {
backgroundImage.image = UIImage(named: "Background-Landscape~ipad.png")
} else {
backgroundImage.image = UIImage(named: "Background~ipad.png")
}
default:
break
}
}
This works, but seems fragile. Is there a more correct way to identify the device as something that supports a regular size class in Landscape, before actually being in Landscape mode? Or some way that I've missed to specify the Landscape equivalents for an image set?
You can use size classes in assets catalog or you can have two image sets: one for portrait and one for landspace mode. Then you can access images by asset name. This will reduce your code a little. But I'd recommend to cosider using size classes everywhere it's possible.
So the final solution was:
-Set the Width property on the image asset to Any+Regular and added a couple of Landscape images for the iPhone 6+ and iPad, and UIImage automatically does the rest
-A more elegant way to permit rotation only for those two devices, in both iOS 7 & iOS 8:
override func shouldAutorotate() -> Bool {
let scale = UIScreen.mainScreen().scale
let idiom = UIDevice.currentDevice().userInterfaceIdiom
return ((idiom == .Pad) || (scale > 2.0)) ? true : false
}
So all of the above down to 5 lines of code!

Resources