2016/8/15更新
汎用性高めのExtension集です。後半にライブラリもまとめました。
Swift2.2で確認済みです。
紹介している全てのextensionを導入したリポジトリを公開しています(紹介していないものも含みます)。
詳しくは下記をご覧ください。
iOSアプリをハッカソンで作るときに便利な初期プロジェクトを作ってみた
Swift3版はこちらです。
クラス名の取得
extension NSObject {
class var className: String {
return String(self)
}
var className: String {
return self.dynamicType.className
}
}
MyClass.className //=> "MyClass"
MyClass().className //=> "MyClass"
XIBの登録・取り出し
XIBファイルとクラスを同じ名前にして利用してください。
上記の「クラス名の取得」を利用しています。
UITableView
extension UITableView {
func registerCell<T: UITableViewCell>(type: T.Type) {
let className = type.className
let nib = UINib(nibName: className, bundle: nil)
registerNib(nib, forCellReuseIdentifier: className)
}
func registerCells<T: UITableViewCell>(types: [T.Type]) {
types.forEach { registerCell($0) }
}
func dequeueCell<T: UITableViewCell>(type: T.Type, indexPath: NSIndexPath) -> T {
return self.dequeueReusableCellWithIdentifier(type.className, forIndexPath: indexPath) as! T
}
}
tableView.registerCell(MyCell.self)
tableView.registerCells([MyCell1.self, MyCell2.self])
let cell = tableView.dequeueCell(MyCell.self)
UICollectionView
extension UICollectionView {
func registerCell<T: UICollectionViewCell>(type: T.Type) {
let className = type.className
let nib = UINib(nibName: className, bundle: nil)
registerNib(nib, forCellWithReuseIdentifier: className)
}
func registerCells<T: UICollectionViewCell>(types: [T.Type]) {
types.forEach { registerCell($0) }
}
func registerReusableView<T: UICollectionReusableView>(type: T.Type, kind: String = UICollectionElementKindSectionHeader) {
let className = type.className
let nib = UINib(nibName: className, bundle: nil)
registerNib(nib, forSupplementaryViewOfKind: kind, withReuseIdentifier: className)
}
func registerReusableViews<T: UICollectionReusableView>(types: [T.Type], kind: String = UICollectionElementKindSectionHeader) {
types.forEach { registerReusableView($0, kind: kind) }
}
func dequeueCell<T: UICollectionViewCell>(type: T.Type, forIndexPath indexPath: NSIndexPath) -> T {
return dequeueReusableCellWithReuseIdentifier(type.className, forIndexPath: indexPath) as! T
}
func dequeueReusableView<T: UICollectionReusableView>(type: T.Type, indexPath: NSIndexPath, kind: String = UICollectionElementKindSectionHeader) -> T {
return dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: type.className, forIndexPath: indexPath) as! T
}
}
collectionView.registerCell(MyCell.self)
collectionView.registerCells([MyCell1.self, MyCell2.self])
let cell = collectionView.dequeueCell(MyCell.self)
collectionView.registerReusableView(MyReusableView.self)
collectionView.registerReusableViews([MyReusableView1.self, MyReusableView2.self])
let view = collectionView.dequeueReusableView(type: MyReusableView.self, indexPath: indexPath)
16進数でUIColorの作成
extension UIColor {
convenience init(hex: Int, alpha: Double = 1.0) {
let r = CGFloat((hex & 0xFF0000) >> 16) / 255.0
let g = CGFloat((hex & 0x00FF00) >> 8) / 255.0
let b = CGFloat(hex & 0x0000FF) / 255.0
self.init(red: r, green: g, blue: b, alpha: CGFloat(alpha))
}
}
let color = UIColor.color(0xAABBCC)
最前面のUIViewControllerを取得
extension UIApplication {
var topViewController: UIViewController? {
guard var topViewController = UIApplication.sharedApplication().keyWindow?.rootViewController else { return nil }
while let presentedViewController = topViewController.presentedViewController {
topViewController = presentedViewController
}
return topViewController
}
var topNavigationController: UINavigationController? {
return topViewController as? UINavigationController
}
}
UIApplication.sharedApplication().topViewController
StoryboardのViewControllerを生成
protocol StoryBoardHelper {}
extension StoryBoardHelper where Self: UIViewController {
static func instantiate() -> Self {
let storyboard = UIStoryboard(name: self.className, bundle: nil)
return storyboard.instantiateViewControllerWithIdentifier(self.className) as! Self
}
static func instantiate(storyboard: String) -> Self {
let storyboard = UIStoryboard(name: storyboard, bundle: nil)
return storyboard.instantiateViewControllerWithIdentifier(self.className) as! Self
}
}
extension UIViewController: StoryBoardHelper {}
MyViewController.instantiate() // Storyboardファイルとクラスが同じ名前の場合
MyViewController.instantiate("MyStoryboard")
※ 上記の「クラス名の取得」を利用しています。
XIBのViewを生成
protocol NibHelper {}
extension NibHelper where Self: UIView {
static func instantiate() -> Self {
let nib = UINib(nibName: self.className, bundle: nil)
return nib.instantiateWithOwner(nil, options: nil)[0] as! Self
}
}
extension UIView: NibHelper {}
MyView.instantiate()
※ XIBファイルとクラスは同じ名前にしてください。
※ 上記の「クラス名の取得」を利用しています。
子Viewを全て削除
extension UIView {
func removeAllSubviews() {
for subview in self.subviews {
subview.removeFromSuperview()
}
}
}
view.removeAllSubViews()
Selectorを集約化
private extension Selector {
static let buttonTapped = #selector(MyViewController.buttonTapped(_:))
}
let button = UIButton()
button.addTarget(self, action: .buttonTapped, forControlEvents: .TouchUpInside)
配列でオブジェクトのインスタンスを検索して削除
extension Array where Element: Equatable {
mutating func remove(element element: Element) -> Bool {
guard let index = indexOf(element) else { return false }
removeAtIndex(index)
return true
}
mutating func remove(elements elements: [Element]) {
for element in elements {
remove(element: element)
}
}
}
let array = ["foo", "bar"]
array.remove(element: "foo")
Out of Rangeを防いで、要素を取得
extension CollectionType {
subscript (safe index: Index) -> Generator.Element? {
get {
return indices.contains(index) ? self[index] : nil
}
}
}
let array = [0, 1, 2]
if let item = array[safe: 5] {
// unreachable
}
2つのDictionaryを結合
extension Dictionary {
mutating func merge<S: SequenceType where S.Generator.Element == (Key, Value)>(sequence: S) {
for (key, value) in sequence {
self[key] = value
}
}
}
var dic1 = ["key1": 1]
let dic2 = ["key2": 2]
dic1.merge(dic2) // => ["key1": 1, "key2": 2]
指定範囲内に値を収める
Viewの配置をコードで実装するときに便利です。
extension Comparable {
func clamped(min min: Self, max: Self) -> Self {
if self < min {
return min
}
if self > max {
return max
}
return self
}
}
let x: CGFloat = 20
x.clamped(min: 0, max: 10) // => 10
NSLocalizedStringを使いやすくする
extension String {
var localized: String {
return NSLocalizedString(self, comment: self)
}
func localizedWithOption(tableName tableName: String? = nil, bundle: NSBundle = NSBundle.mainBundle(), value: String = "") -> String {
return NSLocalizedString(self, tableName: tableName, bundle: bundle, value: value, comment: self)
}
}
let message = "Hello".localized
クラスのプロパティを全て出力
extension NSObjectProtocol where Self: NSObject {
var description: String {
let mirror = Mirror(reflecting: self)
return mirror.children.map { element -> String in
let key = element.label ?? "Unknown"
let value = element.value
return "\(key): \(value)"
}
.joinWithSeparator("\n")
}
}
class Hoge: NSObject {
var foo = 1
let bar = "bar"
}
}
Hoge().description // => "foo: 1\nbar: bar"
文字列からNSURLを取得
extension String {
var url: NSURL? {
return NSURL(string: self)
}
}
if let url = "https://example.com".url {
}
UIAlertControllerをBuilderパターンっぽく扱う
Androidアプリも書いている方は欲しくなるはずです。。。
extension UIAlertController {
func addAction(title title: String, style: UIAlertActionStyle = .Default, handler: (UIAlertAction -> Void)? = nil) -> Self {
let okAction = UIAlertAction(title: title, style: style, handler: handler)
addAction(okAction)
return self
}
func addActionWithTextFields(title title: String, style: UIAlertActionStyle = .Default, handler: ((UIAlertAction, [UITextField]) -> Void)? = nil) -> Self {
let okAction = UIAlertAction(title: title, style: style) { [weak self] action in
handler?(action, self?.textFields ?? [])
}
addAction(okAction)
return self
}
func configureForIPad(sourceRect: CGRect, sourceView: UIView? = nil) -> Self {
popoverPresentationController?.sourceRect = sourceRect
if let sourceView = UIApplication.sharedApplication().topViewController?.view {
popoverPresentationController?.sourceView = sourceView
}
return self
}
func configureForIPad(barButtonItem: UIBarButtonItem) -> Self {
popoverPresentationController?.barButtonItem = barButtonItem
return self
}
func addTextField(handler: UITextField -> Void) -> Self {
addTextFieldWithConfigurationHandler(handler)
return self
}
func show() {
UIApplication.sharedApplication().topViewController?.presentViewController(self, animated: true, completion: nil)
}
}
UIAlertController(title: "ログイン", message: "IDを入力してください", preferredStyle: .Alert)
.addTextField { textField in
textField.placeholder = "ID"
}
.addActionWithTextFields(title: "OK") { action, textFields in
// validation
}
.addAction(title: "キャンセル", style: .Cancel)
.show()
UIViewのスクショを撮る
extension UIView {
func screenShot(width width: CGFloat) -> UIImage? {
let imageBounds = CGRect(x: 0, y: 0, width: width, height: bounds.size.height * (width / bounds.size.width))
UIGraphicsBeginImageContextWithOptions(imageBounds.size, true, 0)
drawViewHierarchyInRect(imageBounds, afterScreenUpdates: true)
var image: UIImage?
let contextImage = UIGraphicsGetImageFromCurrentImageContext()
if let cgImage = contextImage.CGImage {
image = UIImage(
CGImage: cgImage,
scale: UIScreen.mainScreen().scale,
orientation: contextImage.imageOrientation
)
}
UIGraphicsEndImageContext()
return image
}
}
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
label.text = "Hello"
label.textAlignment = .Center
label.backgroundColor = .whiteColor()
let image = label.image(width: 200)
ライブラリ
開発を楽にしてくれる汎用性の高いextension系のライブラリをまとめました。
SwiftDate
NSDateの扱いを簡単にしてくれるライブラリ。
let date1 = NSDate(year: 2016, month: 12, day: 25, hour: 14)
let date2 = "2016-01-05T22:10:55.200Z".toDate(DateFormat.ISO8601)
let date3 = "22/01/2016".toDate(DateFormat.Custom("dd/MM/yyyy"))
let date4 = (5.days + 2.hours - 15.minutes).fromNow
let date5 = date4 + 1.years + 2.months + 1.days + 2.hours
より詳しく知りたい方はこちらへ
Chameleon
いい感じのフラットカラーを用意してくれるライブラリ。
let color1 = UIColor.flatGreenColorDark()
let color2 = FlatGreenDark() // 上の短縮版
let color3 = RandomFlatColor()
let color4 = ComplementaryFlatColorOf(color1) // 補色
UIColor.pinkColor().flatten()
FlatGreen.hexValue //=> "2ecc71"
UIColor(averageColorFromImage: image)
コントロールの色を一括変更することもできます。
Chameleon.setGlobalThemeUsingPrimaryColor(FlatBlue(), withSecondaryColor: FlatMagenta(), andContentStyle: UIContentStyle.Contrast)
R.swift
AndroidのR.javaのようにファイル名などをプロパティ化してくれるライブラリ。
Typoがコンパイル時にわかるので、幸せになれます。
Before:
let icon = UIImage(named: "settings-icon")
let font = UIFont(name: "San Francisco", size: 42)
let viewController = CustomViewController(nibName: "CustomView", bundle: nil)
let string = String(format: NSLocalizedString("welcome.withName", comment: ""), locale: NSLocale.currentLocale(), "Arthur Dent")
After:
let icon = R.image.settingsIcon()
let font = R.font.sanFrancisco(size: 42)
let viewController = CustomViewController(nib: R.nib.customView)
let string = R.string.localizable.welcomeWithName("Arthur Dent")
SwiftString
Stringに便利なメソッドを追加してくれるライブラリ。
"foobar".contains("foo") //=> true
",".join([1,2,3]) //=> "1,2,3"
"hello world".split(" ")[1] //=> "world"
"hello world"[0...1] //=> "he"
"hi hi ho hey hihey".count("hi") //=> 3
SwiftyUserDefaults
NSUserDefaultsをSwiftっぽく扱えるライブラリ。
extension DefaultsKeys {
static let username = DefaultsKey<String?>("username")
static let launchCount = DefaultsKey<Int>("launchCount")
}
// 取得と設定
let username = Defaults[.username]
Defaults[.hotkeyEnabled] = true
// 値の変更
Defaults[.launchCount]++
Defaults[.volume] += 0.1
Defaults[.strings] += "… can easily be extended!"
// 配列の操作
Defaults[.libraries].append("SwiftyUserDefaults")
Defaults[.libraries][0] += " 2.0"
// カスタム型もOK
Defaults[.color] = NSColor.whiteColor()
Defaults[.color]?.whiteComponent // => 1.0
TextAttributes
NSAttributedStringを簡単に設定できるライブラリ。
let attrs = TextAttributes()
.font(name: "HelveticaNeue", size: 16)
.foregroundColor(white: 0.2, alpha: 1)
.lineHeightMultiple(1.5)
NSAttributedString("ほげ", attributes: attrs)
Async
Grand Central Dispatch (GCD) を使いやすくするライブラリ。
Before:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
print("This is run on the background queue")
dispatch_async(dispatch_get_main_queue(), {
print("This is run on the main queue, after the previous block")
})
})
After:
Async.background {
print("This is run on the background queue")
}.main {
print("This is run on the main queue, after the previous block")
}
AsyncKit
https://github.com/mishimay/AsyncKit
http://qiita.com/mishimay/items/7df447969a1c38d856d8
複数の非同期処理の終了後に次の処理を行うことができるライブラリ。
let async = AsyncKit<String, NSError>()
async.parallel([
{ done in done(.Success("one")) },
{ done in done(.Success("two")) }
]) { result in
print(result) //=> Success(["one", "two"])
}
```