1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

NSScrollViewの高さを入力文字数に応じて変更する

Last updated at Posted at 2019-05-22

概要

以下のような、入力した文字数に応じてNSScrollViewとウィンドウの高さが変わるようなものを作成する

May-22-2019 17-02-00

GitHub

NSTextViewのカスタムクラスを作成・設定する

  • 下記の通りクラスを割り当てておく

-w1269

テキストが変更されたときの処理

// テキストの変更時に呼ばれる
- (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行あたりの高さが変わってくるのでバランスを取る必要があるか。
// 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を用いて、NSScrollViewframeの高さを更新する
self.superview.superview.frame.size.height = height;

「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;
}

Protocolの定義

  • Windowの大きさを変えるために、NSWindowController用にプロトコルを定義する
@protocol ChangeableHeightTextViewDelegate <NSTextViewDelegate>
- (void)ChangeableHeightTextViewDidChange;  // テキストビューの内容が変更されたときによばれる
@end
  • <NSTextViewDelegate>NSTextViewDelegateを継承して新たなプロトコルを作成することを示す
@property (weak, nonatomic) id <ChangeableHeightTextViewDelegate> delegate;

-w968

※上記のようにエラーが出るのだが、既存の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の高さ+その他要素の高さで計算する
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?