概要
以下のような、入力した文字数に応じてNSScrollViewとウィンドウの高さが変わるようなものを作成する
GitHub
NSTextViewのカスタムクラスを作成・設定する
- 下記の通りクラスを割り当てておく
テキストが変更されたときの処理
// テキストの変更時に呼ばれる
- (void)didChangeText
{
[self updateScrollViewHeight]; // TextViewの含まれるNSSCrollViewのframeのheightを更新する
[self.delegate ChangeableHeightTextViewDidChange]; // 呼び出し元のWindowControllerでウィンドウの高さを変更する
}
-
NSTextView
のメソッドdidChangeText
をoverrideする - やることは下記の2点
-
NSTextView
の含まれるNSSCrollView
の高さを更新する(このクラスで処理する)- (
NSTextView
は自動で大きさが変わるようになっているみたいで、こちらは更新しない)
- (
- NSWindowControllerのdelegateメソッドを呼び出して、Windowのサイズを更新する(プロトコルを定義してdelegate先で処理する)
NSSCrollViewの高さを更新する
static const int kInitialStringHeight = 19; // 1行目の文字列の高さ
static const int kSingleByteStringHeight = 14; // 非日本語文字列の高さ(FontSiZeが12のとき)
//static const int kMultiByteStringHeight = 18; // 日本語文字列の高さ (FontSiZeが12のとき)
static const int kMaximumLineNum = 100; // この行数以上では高さを変更しない
- 以降の計算で使用するマジックナンバーを変数で定義しておく。
- (原始的…他にいい方法はないものか)
/**
@brief NSTextViewの含まれるNSSCrollViewのframeのheightを更新する
*/
- (void)updateScrollViewHeight {
// Calculate height
NSUInteger numberOfLines = [self numberOfLines];
NSUInteger height = 0;
if (numberOfLines <= kMaximumLineNum) {
height = kInitialStringHeight + (numberOfLines - 1) * kSingleByteStringHeight; // 1行目の高さは固定としている
if (height < kInitialStringHeight) {
height = kInitialStringHeight; // 最低の幅を設定
}
}
// Update height
NSScrollView *scrollView = (NSScrollView *) self.superview.superview;
NSRect frame = scrollView.frame;
frame.size.height = height;
scrollView.frame = frame; // 実際のNSScrollViewのframeを更新
}
// Calculate height
NSUInteger numberOfLines = [self numberOfLines];
NSUInteger height = 0;
if (numberOfLines <= kMaximumLineNum) {
height = kInitialStringHeight + (numberOfLines - 1) * kSingleByteStringHeight; // 1行目の高さは固定としている
if (height < kInitialStringHeight) {
height = kInitialStringHeight; // 最低の幅を設定
}
}
- 行数を計算する関数を作成しておく。(後述)
- 行数がわかれば、あとは原始的に計算していく。
- 今回は英語の文字列のみを考慮しているが、日本語を含む場合は1行あたりの高さが変わってくるのでバランスを取る必要があるか。
- NSString 半角・全角の判定あたりを参考にしたら実装は可能そうではある。
// Update height
NSScrollView *scrollView = (NSScrollView *) self.superview.superview;
NSRect frame = scrollView.frame;
frame.size.height = height;
scrollView.frame = frame; // 実際のNSScrollViewのframeを更新
-
NSTextView
の含まれるNSSCrollView
のframeをここで直接更新する - 計算した
height
を用いて、NSScrollView
のframe
の高さを更新する
self.superview.superview.frame.size.height = height;
- 当初は上記のように書いていたが、
Expression is not assignable
とエラーが出た - iOS開発:”Expression is not assignable.”エラーを参照してワンクッション置くように書いている。
「myLabel.frame」はプロパティへのアクセスで、「frame.size」は構造体メンバへのアクセス。これを混ぜているからエラーとなっている。
NSTextViewの文字列の行数を計算する
/**
@brief NSTextViewの文字列の行数を計算する
*/
- (NSInteger)numberOfLines {
NSLayoutManager *layoutManager = [self layoutManager];
NSUInteger numberOfLines = 0; // 行数のカウント用変数
NSUInteger numberOfGlyphs = [layoutManager numberOfGlyphs];
NSRange lineRange; // 現在対象となっている行の、先頭の開始位置と文字数
for (NSUInteger index = 0; index < numberOfGlyphs; numberOfLines++) { // index: 現在見ている文字の位置
(void) [layoutManager lineFragmentRectForGlyphAtIndex:index
effectiveRange:&lineRange];
index = NSMaxRange(lineRange);
}
// 最終行が改行の場合に計算されていないようなので、その補正をする
NSString *text = self.textStorage.string;
if (text.length != 0 && [[text substringFromIndex:text.length - 1] isEqualToString:@"\n"]) {
numberOfLines++;
}
return numberOfLines;
}
- 行数取得に関してはCounting Lines of Text(Apple公式)を参考にした。
- コメントにも書いたが、最終行が改行の場合に計算が合わないのでその補正をしてやるといい。
Protocolの定義
- Windowの大きさを変えるために、
NSWindowController
用にプロトコルを定義する
@protocol ChangeableHeightTextViewDelegate <NSTextViewDelegate>
- (void)ChangeableHeightTextViewDidChange; // テキストビューの内容が変更されたときによばれる
@end
-
<NSTextViewDelegate>
はNSTextViewDelegate
を継承して新たなプロトコルを作成することを示す
@property (weak, nonatomic) id <ChangeableHeightTextViewDelegate> delegate;
※上記のようにエラーが出るのだが、既存のDelegateを継承する場合のdelegateプロパティの書き方が間違っているのだろうか?
NSTextViewの大きさを可視化する
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
// 見やすいようにNSTextViewの範囲を描画する
NSRect bounds = [self bounds];
[[NSColor greenColor] set];
[NSBezierPath strokeRect:bounds];
}
-
NSTextView
の範囲がわかりやすいように描画を行っておく
ウィンドウの大きさを変更する
// MARK:- ChangeableHeightTextViewDelegate Delegate Methods
// TextView内のテキストが変更されたときに呼ばれる
- (void)ChangeableHeightTextViewDidChange {
NSRect windowFrame = self.window.frame;
NSRect scrollViewFrame = _myTextView.superview.superview.frame;
// 20:Windowのヘッダ幅、上下のマージン、
// 20:(入力していってね)の高さ
// 6 :固定テキストとNSSCrollViewの間隔
windowFrame.size.height = (20 + 20 + 6 + 20 + 20) + scrollViewFrame.size.height;
[self.window setFrame:windowFrame display:YES];
}
- ウィンドウの大きさは、
ScrollViewの高さ
+その他要素の高さ
で計算する