UITextViewでマージン/パディングを失う方法は?


487

私が持っているUITextView大量のテキストを表示する私のiOSアプリケーションに。

次に、のオフセットマージンパラメーターを使用して、このテキストをページングしていますUITextView

私の問題はUITextView、フォントサイズと使用する書体によって異なるように見えるため、のパディングが計算を混乱させることです。

したがって、私は質問を投げかけます:のコンテンツを囲むパディングを削除することは可能UITextViewですか?

ご返信をお待ちしております。


9
このQAはほぼ10年前のものです。これはiOSで最も愚かな問題の1つであるため、100,000以上のビューがあります。ちょうど私が現在の2017年に答えたFTRは、合理的で単純な/通常の/受け入れられた解決策です。
Fattie

1
IOS 3.0がリリースされたばかりの2009年にハードコードされた回避策を書いたので、私はまだこれに関するアップデートを入手しています。私は答えを編集して、それが何年も古いことを明確に述べ、受け入れられたステータスを無視しました。
マイケル

回答:


383

2019年の最新情報

これは、iOSの最も愚かなバグの1つです。

ここで与えられるクラスUITextViewFixedは、通常、全体的に最も合理的なソリューションです。

ここにクラスがあります:

@IBDesignable class UITextViewFixed: UITextView {
    override func layoutSubviews() {
        super.layoutSubviews()
        setup()
    }
    func setup() {
        textContainerInset = UIEdgeInsets.zero
        textContainer.lineFragmentPadding = 0
    }
}

インスペクタでscrollEnabledをオフにすることを忘れないでください!

  1. ソリューションはストーリーボードで適切に機能します

  2. ソリューションは実行時に正しく機能します

これで完了です。

一般的には、ほとんどの場合それで十分です。

テキストビューの高さが変化している場合でもオンザフライをUITextViewFixed通常、あなたが必要とするすべてを行います。

(オンザフライで高さを変更する一般的な例は、ユーザーが入力するときに高さを変更することです。)

これがAppleの壊れたUITextViewです...

UITextViewを使用したIBのスクリーンショット

ここにありUITextViewFixedます:

UITextViewFixedを使用したIBのスクリーンショット

もちろんあなたがしなければならないことに注意してください

インスペクタでscrollEnabledをオフにします!

scrollEnabledをオフにすることを忘れないでください!:)


さらにいくつかの問題

(1)いくつかのまれなケース-たとえば、柔軟で動的に変化するセルの高さを持つテーブルのいくつかのケース-Appleは奇妙なことをします:それらは下部に余分なスペースを追加します。いや、本当に!これはiOSで最も腹立たしいことの1つでなければなりません。

上記に追加する「クイックフィックス」は、通常、その狂気に役立ちます。

...
        textContainerInset = UIEdgeInsets.zero
        textContainer.lineFragmentPadding = 0

        // this is not ideal, but you can sometimes use this
        // to fix the "extra space at the bottom" insanity
        var b = bounds
        let h = sizeThatFits(CGSize(
           width: bounds.size.width,
           height: CGFloat.greatestFiniteMagnitude)
       ).height
       b.size.height = h
       bounds = b
 ...

(2)時には、さらに別の微妙なAppleの混乱を修正するために、これを追加する必要があります。

override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) {
    super.setContentOffset(contentOffset, animated: false)
}

(3)間違いなく、以下を追加する必要があります。

contentInset = UIEdgeInsets.zero

直後.lineFragmentPadding = 0UITextViewFixed

ただし...信じられないかもしれませんが、現在のiOS では動作ません。(2019年にチェックされます。)将来、その行を追加する必要がある場合があります。

UITextViewiOSで壊れているという事実は、すべてのモバイルコンピューティングで最も奇妙なことの1つです。この質問は10周年を迎えましたが、まだ確定していません。

最後に、これはテキストフィールドのやや似たヒントです:https//stackoverflow.com/a/43099816/294884

完全にランダムなヒント:最後に「...」を追加する方法

多くの場合、「UILabelのような」UITextViewを使用しています。したがって、省略記号「...」を使用してテキストを切り捨てたい

その場合は、「セットアップ」に3行目のコードを追加します。

 textContainer.lineBreakMode = .byTruncatingTail

高さをゼロにしたい場合、テキストがない場合に役立つヒント

多くの場合、テキストビューを使用してテキストのみを表示します。したがって、ユーザーは実際には何も編集できません。行「0」を使用すると、テキストビューはテキストの行数に応じて高さが自動的に変更されます。

それは素晴らしいですが、テキストがまったくない場合、残念ながら、1行のテキストがある場合と同じ高さになります !! テキストビューが「消える」ことはありません。

ここに画像の説明を入力してください

「消える」には、これを追加してください

override var intrinsicContentSize: CGSize {
    var i = super.intrinsicContentSize
    print("for \(text) size will be \(i)")
    if text == "" { i.height = 1.0 }
    print("   but we changed it to \(i)")
    return i
}

ここに画像の説明を入力してください

(私はそれを「1」にしたので、何が起こっているのかは明らかであり、「0」は問題ありません。)

UILabelはどうですか?

テキストを表示するだけの場合、UILabelにはUITextViewよりも多くの利点があります。UILabelは、このQAページで説明されている問題の影響を受けません。実際、私たち全員が通常「あきらめて」UITextViewを使用する理由は、UILabelの操作が難しいためです。特に、UILabelにパディングを正しく追加するだけでは、途方もなく困難です。実際には、ここで「最終的には」正しくUILabelにパディングを追加する方法についての十分な議論です:https://stackoverflow.com/a/58876988/294884 いくつかのケースでは、動的な高さの細胞と難しいレイアウトを行っている場合は、それが時々ありますUILabelを使用して難しい方法を実行することをお勧めします。


1
私はself.textView.isScrollEnabled = false内部viewDidLayoutSubviews()でやった、それもうまくいった。Appleは私たちにフープを飛び越え
させ

1
動的な高さ(UITableViewAutomaticDimension)を使用して、UITableViewセル、ヘッダー、およびフッターの不自然なUITextView自動レイアウトバグをすべて修正する最善のソリューション。すごい!
Peter Kreinz 2017年

1
@Fattieの回答に対する更新された回答を投稿しましたtranslatesAutoresizingMaskIntoConstraints。これにより、有効化/無効化という特別なトリックを使用して、すべてのインセットを本当に取り除くことができました。この回答だけでは、自動レイアウトを使用してビュー階層のすべてのマージン(いくつかの奇妙な下マージンが消えないようにする)を削除できませんでした。systemLayoutSizeFitting以前はバグが原因で無効なサイズが返されていたを使用したビューサイズの計算についても説明しますUITextView
Fab1n

1
1up for IBDesignable。私はできれば、よく選ばれたプレースホルダーテキストにも
賛成票を投じます

1
テーブルにtextviewsがありましたが、1行のテキストを持つものは高さを正しく計算しませんでした(autolayout)。これを修正するには、didMoveToSuperviewをオーバーライドし、そこでsetupを呼び出す必要がありました。
El Horrible

791

iOS 7.0の場合、contentInsetトリックが機能しないことがわかりました。これは、iOS 7でマージン/パディングを取り除くために使用したコードです。

これにより、テキストの左端がコンテナの左端になります。

textView.textContainer.lineFragmentPadding = 0

これにより、テキストの上部がコンテナの上部に揃います。

textView.textContainerInset = .zero

マージン/パディングを完全に削除するには、両方のラインが必要です。


2
これは2行で機能しますが、5行になるとテキストが途切れます。
livings124 2013年

7
:第二のラインのようにも書くことができることに注意してください self.descriptionTextView.textContainerInset = UIEdgeInsetsZero;
jessepinho

19
lineFragmentPadding0に設定することは、私が探していた魔法です。なぜAppleがUITextViewのコンテンツを他のコントロールに揃えるのが難しいのか、私にはわかりません。
ファットマン2014年

3
これが正解です。このソリューションでは、水平方向のスクロールは発生しません。
マイケル

2
lineFragmentPaddingマージンを変更するためのものではありません。docs から行フラグメントのパディングは、テキストの余白を表すようには設計されていません。代わりに、テキストビューでインセットを使用するか、段落のマージン属性を調整するか、スーパービュー内のテキストビューの位置を変更する必要があります。
Martin Berger

256

この回避策は、IOS 3.0がリリースされた2009年に書かれました。それはもはや適用されません。

私はまったく同じ問題に遭遇しました、結局私は使用することをしなければなりませんでした

nameField.contentInset = UIEdgeInsetsMake(-4,-8,0,0);

ここで、nameFieldはUITextViewです。たまたま使っていたフォントはHelvetica 16ポイントでした。私が描いていた特定のフィールドサイズに対するその唯一のカスタムソリューション。これにより、左側のオフセットが左側と同じ高さになり、上部のオフセットが、ボックスが描画される場所に配置されます。

さらに、これUITextViewsはデフォルトのアラインメントを使用している場所にのみ適用されるようです。

nameField.textAlignment = NSTextAlignmentLeft;

たとえば右に揃えると、右端UIEdgeInsetsMakeにはまったく影響がないようです。

少なくとも、.contentInsetプロパティを使用すると、フィールドを「正しい」位置に配置し、をオフセットすることなく偏差に対応できますUITextViews


1
はい、iOS 7では機能します。UIEdgeInsetが正しい値に設定されていることを確認してください。UIEdgeInsetsMake(-4、-8,0,0)とは異なる場合があります。
app_ 2013

15
IOS 7では、UIEdgeInsetsMake(0、-4,0、-4)が最も効果的であることがわかりました。
Racura 2013年

2
はい、これらはサンプル番号です。「私が描いていた特定のフィールドサイズのカスタムソリューションにすぎません」主に私はあなたがあなたのユニークなレイアウト状況のために数字をいじらなければならないことを説明しようとしていました。
マイケル

3
UITextAlignmentLeftはiOS 7では非推奨です。NSTextAlignmentLeftを使用してください。
Jordi Kroon

7
ハードコーディングされた値を利用する答えに人々が賛成する理由はわかりません。これは、iOSの将来のバージョンで機能しなくなる可能性が非常に高く、単なる悪い考えです。
ldoogy

77

すでに与えられている良い答えのいくつかを基にして、iOS 7.0以降で機能する純粋にストーリーボード/インターフェイスビルダーベースのソリューションを以下に示します

UITextViewのユーザー定義ランタイム属性を次のキーに設定します。

textContainer.lineFragmentPadding
textContainerInset

インターフェースビルダー


4
これは明らかに最良の答えです。特にXIBのみのソリューションが必要な場合
yano

16
コピー/貼り付け:textContainer.lineFragmentPadding | textContainerInset
Luca De Angelis

3
これが美しい答えです-3年後でも簡単でうまく機能します:)
Shai Mishali


41

実際のマージンはユーザーのフォントサイズ設定などで変化する可能性があるため、ハードコードされた値に関する回答は絶対に避けます。

これは@ user1687195の回答です。変更せずに記述されていますtextContainer.lineFragmentPaddingドキュメントではこれは意図された使用法ではないため)。

これはiOS 7以降に最適です。

self.textView.textContainerInset = UIEdgeInsetsMake(
                                      0, 
                                      -self.textView.textContainer.lineFragmentPadding, 
                                      0, 
                                      -self.textView.textContainer.lineFragmentPadding);

これは事実上同じ結果ですが、lineFragmentPaddingプロパティを誤用しないという点で少しすっきりしています。


私はこの答えに基づいて別の解決策を試しましたが、うまくいきました。解決策は以下のとおりです。self.textView.textContainer.lineFragmentPadding = 0;
Bakyt Abdrasulov

1
@BakytAbdrasulov私の答えを読んでください。あなたの解決策は機能しますが、ドキュメントに従っていません(私の答えのリンクを参照してください)。lineFragmentPaddingマージンを制御するためのものではありません。そのため、を使用することになっていますtextContainerInset
ldoogy 2017年

21

ユーザー定義のランタイム属性を使用したスト​​ーリーボードまたはインターフェイスビルダーソリューション:

スクリーンショットは、iOS 7.1およびiOS 6.1とのものcontentInset = {{-10, -5}, {0, 0}}です。

ユーザー定義のランタイム属性

出力


1
iOS 7で私のために働いた
kubilay 2014年

ユーザー定義のランタイム属性だけのために-すごい!
hris.to 2014

16

これらすべての回答はタイトルの質問に対応していますが、OPの質問の本文に示されている問題の解決策をいくつか提案したいと思いました。

テキストコンテンツのサイズ

内部のテキストのサイズを計算する簡単な方法UITextViewは、NSLayoutManager

UITextView *textView;
CGSize textSize = [textView usedRectForTextContainer:textView.textContainer].size;

これにより、スクロール可能なコンテンツの合計が得られます。これは、UITextViewのフレームよりも大きい場合があります。これtextView.contentSizeは、テキストが占めるスペースを実際に計算するので、より正確であることがわかりました。たとえば、空の場合UITextView

textView.frame.size = (width=246, height=50)
textSize = (width=10, height=16.701999999999998)
textView.contentSize = (width=246, height=33)
textView.textContainerInset = (top=8, left=0, bottom=8, right=0)

行の高さ

UIFont指定されたフォントの行の高さをすばやく取得できるプロパティがあります。だからあなたはあなたの中UITextViewでテキストの行の高さをすばやく見つけることができます:

UITextView *textView;
CGFloat lineHeight = textView.font.lineHeight;

表示されるテキストサイズの計算

「ページング」効果を処理するには、実際に表示されるテキストの量を決定することが重要です。UITextViewと呼ばれるプロパティがあり、textContainerInsetこれは実際にはUITextView.frameテキストとテキスト自体の間のマージンです。可視フレームの実際の高さを計算するには、次の計算を実行できます。

UITextView *textView;
CGFloat textViewHeight = textView.frame.size.height;
UIEdgeInsets textInsets = textView.textContainerInset;
CGFloat textHeight = textViewHeight - textInsets.top - textInsets.bottom;

ページングサイズの決定

最後に、テキストサイズとコンテンツが表示されたので、textHeightからを引くことで、オフセットをすばやく決定できますtextSize

// where n is the page number you want
CGFloat pageOffsetY = textSize - textHeight * (n - 1);
textView.contentOffset = CGPointMake(textView.contentOffset.x, pageOffsetY);

// examples
CGFloat page1Offset = 0;
CGFloat page2Offset = textSize - textHeight
CGFloat page3Offset = textSize - textHeight * 2

これらの方法をすべて使用して、インセットに触れずに、キャレットまたはテキスト内の好きな場所に移動できました。


12

次のtextContainerInsetプロパティを使用できますUITextView

textView.textContainerInset = UIEdgeInsetsMake(10、10、10、10);

(上、左、下、右)


1
なぜこれがベストアンサーとして割り当てられていないのかしら。textContainerInsetは私のニーズを本当に満たしました。
KoreanXcodeWorker

textContainerInsetプロパティの一番下の値を新しい値に変更したい場合、それを更新しても機能しません。stackoverflow.com/ questions / 19422578 /…を参照してください。
エヴァンR

@MaggiePhillipsハードコードされた値はこれを行う正しい方法ではない可能性があり、場合によっては失敗することが保証されているため、これは最良の答えではありません。lineFragmentPaddingを考慮する必要があります。
ldoogy

11

iOS 10の場合、次の行は上部と下部のパディングの削除に機能します。

captionTextView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)

Xcode 8.2.1。この回答で述べたのと同じ問題がまだあります。私の解決策は、値をUIEdgeInsetsとして編集することでした(上:0、左:-4.0、下:0、右:-4.0)。
Darkwonder 2017

UIEdgeInset.zeroXCode 8.3とiOS 10.3 Simulatorを使用して動作します
Simon Warta

10

最新のSwift:

self.textView.textContainerInset = .init(top: -2, left: 0, bottom: 0, right: 0)
self.textView.textContainer.lineFragmentPadding = 0

すばらしい答えです。この回答は、追加のトップインセットを削除します。textView.textContainerInset = UIEdgeInsets.zeroは、上のインセットから2ピクセルを削除しません。
korgx9

10

これは、Fattieの非常に役立つ回答の更新版です。これは、iOS 10と11(おそらくそれより下のものでも)でレイアウトを機能させるのに役立つ2つの重要な行を追加します。

@IBDesignable class UITextViewFixed: UITextView {
    override func layoutSubviews() {
        super.layoutSubviews()
        setup()
    }
    func setup() {
        translatesAutoresizingMaskIntoConstraints = true
        textContainerInset = UIEdgeInsets.zero
        textContainer.lineFragmentPadding = 0
        translatesAutoresizingMaskIntoConstraints = false
    }
}

重要な行は2つのtranslatesAutoresizingMaskIntoConstraints = <true/false>ステートメントです!

これは驚くべきことに、私のすべての状況ですべてのマージン削除します

一方でtextView最初の応答者ではありません、使用して解くことができなかったいくつかの奇妙な下マージンがあることが起こる可能性がsizeThatFits受け入れ答えに記載されている方法を。

textViewをタップすると、奇妙な下マージンが突然消え、すべてが正常に表示されましたが、textViewが取得されたときのみfirstResponderです。

だから私はここでSOを読んでいますが、有効化と無効化translatesAutoresizingMaskIntoConstraintsは呼び出しの間に手動でフレーム/境界を設定するときに役立ちます。

幸い、これはフレーム設定だけでなくsetup()、2つのtranslatesAutoresizingMaskIntoConstraints呼び出しの間に挟まれた2本の線でも機能します。

たとえば、これは、でを使用してビューのフレームを計算するときに非常に役立ちます。正しいサイズを返します(以前はありませんでした)。systemLayoutSizeFittingUIView

元の回答のように:
インスペクターでscrollEnabledをオフにすることを忘れないでください!
そのソリューションは、ストーリーボードでも実行時でも適切に機能します。

それだけです、これで本当に完了です!


5

Swift 4、Xcode 9

次の関数を使用して、UITextViewのテキストのマージン/パディングを変更できます

public func UIEdgeInsetsMake(_ top:CGFloat、_ left:CGFloat、_ bottom:CGFloat、_ right:CGFloat)-> UIEdgeInsets

この場合は

 self.textView?.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)

3

インセットソリューションを実行しても、右側と下部にパディングが残っていました。また、テキストの配置が問題を引き起こしていました。私が見つけた唯一の確実な方法は、境界にクリップされた別のビューの中にテキストビューを置くことでした。


3

これは、アプリのすべてのテキストビューからAppleのデフォルトのマージンを削除する簡単な小さな拡張機能です。

注:Interface Builderは古いマージンを表示しますが、アプリは期待どおりに動作します。

extension UITextView {

   open override func awakeFromNib() {
      super.awakeFromNib();
      removeMargins();
   }

   /** Removes the Apple textview margins. */
   public func removeMargins() {
      self.contentInset = UIEdgeInsetsMake(
         0, -textContainer.lineFragmentPadding,
         0, -textContainer.lineFragmentPadding);
   }
}

1

私(iOS 11とXcode 9.4.1)にとって魔法のように機能したのは、textView.fontプロパティをUIFont.preferred(forTextStyle:UIFontTextStyle) スタイルに設定し、@ Fattieによって言及された最初の回答でもありました。しかし、@ Fattieの回答は、textView.fontプロパティを設定するまで機能しませんでした。そうしないと、UITextViewの動作が不安定になります。


1

最新のSwiftバージョンを探している人がいる場合、以下のコードはXcode 10.2Swift 4.2で正常に動作しています

yourTextView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

0

UITextViewのサブビューからテキストでビューを取得し、サブクラスのlayoutSubviewメソッドで設定するもう1つの方法を見つけました。

- (void)layoutSubviews {
    [super layoutSubviews];

    const int textViewIndex = 1;
    UIView *textView = [self.subviews objectAtIndex:textViewIndex];
    textView.frame = CGRectMake(
                                 kStatusViewContentOffset,
                                 0.0f,
                                 self.bounds.size.width - (2.0f * kStatusViewContentOffset),
                                 self.bounds.size.height - kStatusViewContentOffset);
}

0

textViewのスクロールもテキストの位置に影響し、垂直方向に中央揃えされていないように見えます。スクロールを無効にし、上部インセットを0に設定することで、ビューのテキストを中央に配置することができました。

    textView.scrollEnabled = NO;
    textView.textContainerInset = UIEdgeInsetsMake(0, textView.textContainerInset.left, textView.textContainerInset.bottom, textView.textContainerInset.right);

なんらかの理由でまだわかりません。入力が始まる前にカーソルはまだ中央に配置されていませんが、入力を開始するとすぐにテキストが中央に配置されます。


0

HTML文字列を設定し、下のパディングを避けたい場合は、div、pなどのブロックタグを使用していないことを確認してください。

私の場合、これが理由でした。ブロックタグの出現箇所をie spanタグに置き換えることで、簡単にテストできます。


0

SwiftUIの場合

関数を使用して独自のTextViewを作成しUIViewRepresentable、パディングを制御する場合は、次のmakeUIViewようにします。

uiTextView.textContainerInset = UIEdgeInsets(top: 10, left: 18, bottom: 0, right: 18)

またはあなたが望むものは何でも。


-4
[firstNameTextField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.