はじめに
iPhone6とiPhone6 plusが登場してから3.5inch,4.0inch,4.7inch,5.5inchと対応する画面解像度が増えてきており、画面のレイアウトを組むうえで複数解像度での見た目を気にしなくてはならなくなりました。
特に画面の比率が変わってしまうことによって考えていたデザインが解像度によって崩れてしまったり、見切れてしまうなんてことが起こることもありました。
最近ではInterface Builder
が高機能化し、Auto Layout
やSize Classes
など多画面対応を意識したレイアウト構築ができるようになっています。
しかしながら、デザインの構造上やアニメーションをやりたい時などどうしてもソースコードでレイアウトを組まなければならない場合があります。
個人的によくやるのが下記のような感じで、画面比率を計算してフレームを決定していました。
let DesignedWidth : CGFloat = 375.0 //iPhone6 resolution
let ratio = UIScreen.mainScreen().bounds.width / DesignedWidth
let frame = CGRectMake(0, 0, self.view.frame.width, 200 * ratio)
横幅をviewの幅として、高さを画面比率に応じて変えていく感じです。
ただ、これだとソースコード中にratio
を掛ける処理が大量に発生してしまうので可読性もメンテナンス性も低下していまいます。
そこでExtensionでここらへんの処理をまとめてみました。
環境
- Xcode 7.2
- swift 2.1
実装
まず画面比率の計算は1度やればいいので、シングルトン化してしまいます。
(ターゲットを分けて開発しているのでアクセス装飾子がついていますが、そうでない方は適宜読み替えていただけると助かります。)
//MARK: - Resolution ratio
private let DesignedWidth : CGFloat = 375.0 //iPhone6 resolution
private class Resolution {
class var ratio : CGFloat {
struct Static {
static let ratio = UIScreen.mainScreen().bounds.width / DesignedWidth
}
return Static.ratio
}
}
private var ResolutionRatio : CGFloat {
return Resolution.ratio
}
次にちょっとしたラッパーを用意
//MARK: - Wrapper
public func CGFloatResized(value: CGFloat) -> CGFloat {
return value * ResolutionRatio
}
本題のExtensionを実装
OptionSetType
でフレームのリサイズする箇所を指定できるようにします。
//MARK: - Option
public struct ResizeType : OptionSetType {
public let rawValue : UInt32
public init (rawValue : UInt32) { self.rawValue = rawValue }
public static let X = ResizeType(rawValue: 0x1 << 0)
public static let Y = ResizeType(rawValue: 0x1 << 1)
public static let Width = ResizeType(rawValue: 0x1 << 2)
public static let Height = ResizeType(rawValue: 0x1 << 3)
public static let Point = ResizeType([X, Y])
public static let Size = ResizeType([Width, Height])
public static let Rect = ResizeType([Point, Size])
}
//MARK: - Extensiton
public extension CGSize {
public func resize(type : ResizeType) -> CGSize {
var size = self
if type.contains(.Width) { size.width = CGFloatResized(size.width) }
if type.contains(.Height) { size.height = CGFloatResized(size.height) }
return size
}
}
public extension CGPoint {
public func resize(type : ResizeType) -> CGPoint {
var point = self
if type.contains(.X) { point.x = CGFloatResized(point.x) }
if type.contains(.Y) { point.y = CGFloatResized(point.y) }
return point
}
}
public extension CGRect {
public func resize(type : ResizeType) -> CGRect {
var rect = self
if type.contains(.X) { rect.origin.x = CGFloatResized(rect.origin.x) }
if type.contains(.Y) { rect.origin.y = CGFloatResized(rect.origin.y) }
if type.contains(.Width) { rect.size.width = CGFloatResized(rect.size.width) }
if type.contains(.Height) { rect.size.height = CGFloatResized(rect.size.height) }
return rect
}
}
public extension UIFont {
public class func systemFontOfSizeResized(fontSize : CGFloat) -> UIFont {
return UIFont.systemFontOfSize(CGFloatResized(fontSize))
}
public class func boldSystemFontOfSizeResized(fontSize : CGFloat) -> UIFont {
return UIFont.boldSystemFontOfSize(CGFloatResized(fontSize))
}
}
使用例
let frame = CGRectMake(0, 0, self.frame.width, 200).resize(.Height)
let size = CGSizeMake(100, 100).resize(.Size)
let margin = CGFloatResized(20.0)
let font = UIFont.systemFontOfSizeResized(20)
終わりに
ResizeType
を指定することでリサイズしたい箇所を細かくすることができます。
下記みなたいな感じでさらにラッパーを用意すれば他の部分を実装をあわせることができます。
public func CGRectMakeResized(x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat, _ type : ResizeType) -> CGRect
本記事の内容は、現在開発しているアプリで試験的に導入してみたもので、実用性についてはまだ未知数なので、追って検証していこうと思っています。
もしこういった問題でもっと良い手法をご存知の方がいらっしゃればご教授願いたいです!