iOSのアプリ開発でUITableViewを使って一覧表示をすることがあると思います。

その時に、一つ一つのUITableViewCellの大きさを動的に変える必要があって、いちいち計算するロジックをUITableViewDelegate- (CGFloat)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPathで書いちゃうのはなんとなく汚くていやなので、僕はいつもNSStringのCategoryを作って簡単に必要なCGSizeなりCGRectを返すメソッドを実装して使っていました。

そんなことをSwiftでもやろうと思い、やってみたときにちょっとつまずいて解決したので、その解決方法を紹介します。

Objective-CでいうのCategoryはSwiftだとextension

Objective-CCategoryやらSwiftextensionとはこういうものという説明は今回割愛させて頂きます。

別記事で書くかもです。

簡単に紹介しますと、既存のクラス(今日紹介するのだとNSStringクラスとかStringクラス)にメソッドを追加できるやつです。(もっと奥が深いですが、とっても平たく言うとです。)

それを使ってObjective-CではNSStringを拡張して幅や高さを返すメソッドを追加していました。

どんなソースコードか紹介します。

Objective-Cで文字列の高さを取得する

こんな感じです。

NSString+GetTextSize.h
1
2
3
4
#import <Foundation/Foundation.h>
@interface NSString (GetTextSize)
- (CGSize)getTextSizeWithFont:(UIFont *)font viewWidth:(CGFloat)width padding:(CGFloat)padding;
@end
NSString+GetTextSize.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import "NSString+GetTextSize.h"
@implementation NSString (GetTextSize)
- (CGSize)getTextSizeWithFont:(UIFont *)font viewWidth:(CGFloat)width padding:(CGFloat)padding
{
CGSize size;
if ([self respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) {
CGSize bounds = CGSizeMake(width, CGFLOAT_MAX);
size = [self boundingRectWithSize:bounds options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) attributes:@{NSFontAttributeName: font} context:nil].size;
} else {
size = CGSizeZero;
}
size.height += padding;
return size;
}
@end

上記のようなCategoryを用意すると

1
2
3
4
5
6
7
NSString *text = @"hogehogehogehogehogehoge";
CGFloat viewMargin = 8.0f;
CGFloat viewPadding = 5.0f;
CGFloat viewWidth = self.tableView.frame.size.width - (viewMargin * 2);
UIFont *textFont = [UIFont systemFontOfSize: 17.0];
CGSize *textSize = [text getTextSizeWithFont:font viewWidth:viewWidth padding:(viewPadding * 2)];
CGFloat height = textSize.height;

みたいな感じで使う事が出来ます。

それではこれをSwiftでやってみたいと思います。

Swiftで文字列の高さを取得する

まず先に言っておくと、ここでつまずきました。

色々調べた結果、Xcodeのバグという事が判明したので、ひとまずObjective-Cのクラスを使って対応しました。

で、SwiftObjective-Cのクラスを使うにはBridging-Headerというヘッダーファイルを作成しなければなりません。

今回はXcode側で勝手に作成できるので、それを使いましたが、Objective-Cのライブラリを使うときはこれが必要です。

Bridging-Headerの追加方法は別記事にして書きたいと思います。

ということで、ソースコードを紹介します。

StringDrawingOptions.h
1
2
3
4
#import <Foundation/Foundation.h>
@interface StringDrawingOptions : NSObject
+ (NSStringDrawingOptions)combine:(NSStringDrawingOptions)option1 with:(NSStringDrawingOptions)option2;
@end
StringDrawingOptions.m
1
2
3
4
5
6
7
8
#import "StringDrawingOptions.h"
@implementation StringDrawingOptions
+ (NSStringDrawingOptions)combine:(NSStringDrawingOptions)option1 with:(NSStringDrawingOptions)option2
{
return (option1 | option2);
}
@end
StringExtension.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
import UIKit
extension String {
func getTextSize(font:UIFont, viewWidth:CGFloat, padding:CGFloat) -> CGSize {
var size:CGSize = CGSizeZero
if let s:CGSize = self.makeSize(viewWidth, font: font) {
size = CGSize(width: s.width, height: s.height + padding)
}
return size
}
// MARK: private
func makeSize(width:CGFloat, font:UIFont) -> CGSize? {
var size:CGSize? = nil
if self.respondsToSelector("boundingRectWithSize:options:attributes:context:") {
let bounds:CGSize = CGSize(width: width, height: CGFloat.max)
let attributes: Dictionary = [NSFontAttributeName: font]
// let options:NSStringDrawingOptions = (NSStringDrawingOptions.UsesLineFragmentOrigin | NSStringDrawingOptions.UsesFontLeading)
let options:NSStringDrawingOptions = StringDrawingOptions.combine(NSStringDrawingOptions.UsesLineFragmentOrigin, with: NSStringDrawingOptions.UsesFontLeading)
let rect:CGRect = self.boundingRectWithSize(bounds, options: options, attributes: attributes, context: nil)
size = CGSize(width: rect.size.width, height: rect.size.height)
}
return size
}
}

以下の所でエラーが出て、使えませんでした。

1
2
// let options:NSStringDrawingOptions = (NSStringDrawingOptions.UsesLineFragmentOrigin | NSStringDrawingOptions.UsesFontLeading)
let options:NSStringDrawingOptions = StringDrawingOptions.combine(NSStringDrawingOptions.UsesLineFragmentOrigin, with: NSStringDrawingOptions.UsesFontLeading)

コメントアウトされている方が、ダメなやつです。

使い方は以下のような感じです。

1
2
3
4
5
6
7
let text: String = "hogehogehogehogehoge"
let viewMargin: CGFloat = 8.0
let viewPadding: CGFloat = 5.0
let viewWidth: CGFloat = self.tableView.frame.size.width - (viewMargin * 2)
let textFont: UIFont = UIFont.systemFontOfSize(17.0)
let textSize: CGSize = text.getTextSize(font, viewWidth: viewWidth, padding: viewpadding)
let height: CGFloat = textSize.height

ちなみに僕はvarが大っ嫌いです。

終わりに

こんな感じで、簡単なObjective-CのソースコードをSwiftに置き換えるのに時間がかかりましたが、最初はこんな感じでアップしていくのがいいのかなと思いました。

ただやっぱiOSObjective-Cで書くのが醍醐味なんじゃないかなと思ってます。

書けと言われれば書きますが!

では以上になります。