Getting device aspect ratio in Xcode programmatically - ios

I'm making a universal game across all apple platforms. The problem is there are lots of aspect ratios, and with the growing numbers of devices, it becomes a hassle. I've tried the following:
var deviceAspectRatio: CGFloat? {
#if os(iOS)
if UIDevice.current.model.contains("iPhone") {
return 16/9
} else if UIDevice.current.model.contains("iPad") {
return 4/3
}
#elseif os(tvOS)
return 16/9 //There might be other aspect ratios also
#elseif os(watchOS)
return 1
#elseif os(macOS)
//figure out aspect ratio
#else
return nil
#endif
}
But even with this, Xcode gives me an error:
Missing return in a function expected to return 'CGFloat?'

The trick on macOS is that there might be more than one screen, so if this is the case, you'll have to decide which one you're interested in. However, if you settle on a screen, you can just get the frame from NSScreen.frame and divide the width by the height.
This code will get the aspect ratio for the screen a given window is on:
guard let frame = someWindow.screen?.frame else { return nil }
let aspectRatio = NSWidth(frame) / NSHeight(frame)
Also, you should probably be doing something similar with UIScreen on iOS instead of hard-coding the values there. Apple may someday release a new device with some other aspect ratio your app doesn't anticipate.

In order to make it universal code for all devices you can use DeviceKit dependency and then
import DeviceKit
let device = Device.current
let deviceAspectRatio = device.screenRatio.height / device.screenRatio.width
OR
let deviceWidth = UIScreen.main.bounds.width * UIScreen.main.scale
let deviceHeight = UIScreen.main.bounds.height * UIScreen.main.scale
let testDeviceAspectRatio = deviceHeight / deviceWidth

Related

Get iPhone model XS Max by screen height

I'm trying to customise the design for XS Max. Previously I just identified the device by checking the main.bound.screen.height, which, for XS Max, according to this site: https://www.paintcodeapp.com/news/ultimate-guide-to-iphone-resolutions, should return 896.0, right?
The Xcode 10.0 XS Max simulator returns 812.0 as screen height (the same goes for XR, which should have the same screen points), though. Any idea how to fix this?
enum IPhone: String {
case SE, normal, plus, X, XSMax
init?(height: CGFloat) {
switch height {
case 568: self = .SE
case 667: self = .normal
case 736: self = .plus
case 812: self = .X
case 896: self = .XSMax
default:
return nil
}
}
}
struct Design {
static var iPhone = IPhone(height: UIScreen.main.bounds.height)!
}
In the viewDidLoad() of the view controller:
print("screen height: \(UIScreen.main.bounds.height), iPhone: \(Design.iPhone.rawValue)")
Ok, after some searching I've found the answer to my question here: UIScreen MainScreen Bounds returning wrong size
Turns out, Xcode will guess the size of the screen by going through the launch images you've provided. Can you believe that? So if you didn't provide an image in the proper size for the new XS Max it will fall back to the biggest launch image it finds. That's why in a new project without launch images it worked fine.
I've provided launch images for XS Max and XR and now the screen sizes are correct.

Screen scale factor in XCTest

I am running UITest in which it's crucial to get the screen scale factor. Usually I've used UIScreen.main.scalem but XCUIScreen.main does not seem to have scale property.
Is it possible to access device scale factor in UITests?
I've managed it by adding AccessibilityIdentifier to window in AppDelegate that contains info needed:
int scaleFactor = [[NSNumber numberWithDouble:[UIScreen mainScreen].scale] intValue];
self.window.accessibilityIdentifier = [NSString stringWithFormat:#"windowScale:%d", scaleFactor];
And later accessing it and parsing in my tests:
var windowsScaleFactor: Int {
let scaleFactorString = self.windows.firstMatch.identifier
guard let scaleFactor = Int(scaleFactorString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined(separator: "")) else {
fatalError("Could not determine window scale factor")
}
return scaleFactor
}

iOS screen size in code, is this optional?

In some apps theres settings for screen sizes. Vs regular constraint settings, whats the advantage vs disadvantage?
example:
struct Setting {
struct Small {
static let phoneSE: CGFloat = 11.0
static let phone: CGFloat = 13.0
static let phonePlus: CGFloat = 14.0
}
struct Medium {
static let phoneSE: CGFloat = 12.0
static let phone: CGFloat = 14.0
static let phonePlus: CGFloat = 15.0
}
struct Large {
static let phoneSE: CGFloat = 13.0
static let phone: CGFloat = 15.0
static let phonePlus: CGFloat = 16.0
}
These values are specific font size values.
The values often change for the purpose, some values might be font size or img sizes. Is something like this a good practice or overkill?
It usually isn't "good practice" to code against specific devices. Or in other words, it is "good practice to code against size classes (using auto layout constraints) and use Dynamic Type (instead of specific font sizes) where possible. Why? Because when Apple releases something new - like the 10.5 inch iPad Pro last April or the rumored iPhone Pro this September - you won't have to do much, if anything to your code.
It will "just work".
That said, here's some reasons you may need to code against specific devices - or more accurately, specific screen sizes:
UI changes for orientation on an iPad and iPhone "Plus". Size classes are always regular no matter the orientation.
Font sizes, like in your example.
I'm sure there's more. But in these two cases - and really, in all cases - it's "good practice" to try hard to use what Apple gives you. Refactor your UI if possible. Save on unnecessary ongoing maintenance.
It depends on your requirements.
If you need to display different size depend on device you can create structure for that if not than you can use fix size.

SpriteKit how to get correct screen size

I've tried
self.frame.size
self.view!.frame.size
UIScreen.mainScreen().bounds.size
None of them work.
How do I get the correct screen size for the device?
You can use following swift code to get the screen size.
let displaySize: CGRect = UIScreen.mainScreen().bounds
let displayWidth = displaySize.width
let displayHeight = displaySize.height
You can use:
let deviceWidth = UIScreen.mainScreen().bounds.width
let deviceHeight = UIScreen.mainScreen().bounds.height
And if you need to get the ratio so you can detect what device they are using, you can use the following for portrait mode or switch it for landscape mode.
let maxAspectRatio: CGFloat = deviceHeight / deviceWidth
You can then check what ratio the device is to determine certain things.
if maxAspectRatio == (4 / 3) {
//The ratio of an iphone 4S is 4:3
print("The user is using an iPhone 4S or iPod 4th Generation.")
} else if maxAspectRatio >= 1.7 && maxAspectRatio <= 1.8 {
//The ratio of these devices is 16:9
print("The user is using an iPhone 5, 5S, 5C, 6, 6S, 6 Plus, 6S Plus, or iPod 5th Generation.)
} else {
print("The user is using an iPad, iPad Mini, iPad Air, iPad Retina, or iPad Pro.")
}
If you want to get the correct view that the player can see, as in the bounding frame of the device's size, you can use the following guide.

Centre of 4.7 Inch view is not 333.5?

I have an if statement in my app to detect whether or not to move the view up when the keyboard appears. I have a separate one of 3.5/4, 4.7 and 5.5 inch screens.
The ones for the 3.5/4 and 5.5 inch screens work great, however for some reason, the one for the 4.7 inch screen isn't functioning.
This is my code:
if keyboardActive == false && height == 667 && self.entryView.center.y == 333.5 {
If I remove self.entryView.center.y == 333.5 then it works, so that's the problem. I've tried rounding up to 334 and down to 333 but that didn't help.
Does anyone know why the centre y value is not 333.5?
comparing floating point numbers is problematic. See for example: (How should I do floating point comparison?).
Moving your screen based on literals is also problematic. Better would be to get the frame of the keyboard in view local coordinates and resize your view accordingly. Note that if you are using constraints, you will have to adjust the constraints rather than the frame of your view(s).
Here's a handy function I wrote for the purpose:
private func extractKeyboardInfo(userInfo: NSDictionary) -> (keyboardFrame: CGRect, duration: NSTimeInterval, viewAnimationOptions: UIViewAnimationOptions) {
let globalKeyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as NSValue).CGRectValue()
let localKeyboardFrame = self.view.convertRect(globalKeyboardFrame, fromView: nil)
let curve = UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] as NSNumber).unsignedIntValue << 16)
let viewAnimationOptions = UIViewAnimationOptions.fromRaw(curve)!
let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as NSNumber).doubleValue as NSTimeInterval
return (localKeyboardFrame, duration, viewAnimationOptions)
}

Resources