Edited at

コードで定義したレイアウトの多画面対応をちょっと楽にするExtension

More than 3 years have passed since last update.


はじめに

iPhone6とiPhone6 plusが登場してから3.5inch,4.0inch,4.7inch,5.5inchと対応する画面解像度が増えてきており、画面のレイアウトを組むうえで複数解像度での見た目を気にしなくてはならなくなりました。

特に画面の比率が変わってしまうことによって考えていたデザインが解像度によって崩れてしまったり、見切れてしまうなんてことが起こることもありました。

最近ではInterface Builderが高機能化し、Auto LayoutSize 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

本記事の内容は、現在開発しているアプリで試験的に導入してみたもので、実用性についてはまだ未知数なので、追って検証していこうと思っています。

もしこういった問題でもっと良い手法をご存知の方がいらっしゃればご教授願いたいです!


参考資料