2

I am using a textfield with these properties:

 TextField(
    controller: textController,    
    keyboardType: TextInputType.multiline,
    maxLines: 4,
    maxLength: 150,
 ),

Which works fine but I was wondering how I could prevent users from typing in break lines that would cause the text field to have more lines that the maxLines (4)..

Is there a way of locking the lines at 4? e.g.

input 1 \n \n \n should work

but 1 \n \n \n \n \n \n should not be allowed

ebg11
  • 906
  • 1
  • 12
  • 33

3 Answers3

8

I modified LengthLimitingTextInputFormatter to get my own MaxLinesTextInputFormatter.

Here is the code

class MaxLinesTextInputFormatter extends TextInputFormatter {
  MaxLinesTextInputFormatter(this.maxLines)
      : assert(maxLines == null || maxLines == -1 || maxLines > 0);

  final int maxLines;

  @override
  TextEditingValue formatEditUpdate(
    TextEditingValue oldValue, // unused.
    TextEditingValue newValue,
  ) {
    if (maxLines != null && maxLines > 0) {
      final regEx = RegExp("^.*((\n?.*){0,${maxLines - 1}})");
      String newString = regEx.stringMatch(newValue.text) ?? "";

      final maxLength = newString.length;
      if (newValue.text.runes.length > maxLength) {
        final TextSelection newSelection = newValue.selection.copyWith(
          baseOffset: math.min(newValue.selection.start, maxLength),
          extentOffset: math.min(newValue.selection.end, maxLength),
        );
        final RuneIterator iterator = RuneIterator(newValue.text);
        if (iterator.moveNext())
          for (int count = 0; count < maxLength; ++count)
            if (!iterator.moveNext()) break;
        final String truncated = newValue.text.substring(0, iterator.rawIndex);
        return TextEditingValue(
          text: truncated,
          selection: newSelection,
          composing: TextRange.empty,
        );
      }
      return newValue;
    }
    return newValue;
  }
}

Usage:

TextField(
  decoration: InputDecoration(),
  maxLines: 4,
  inputFormatters: [MaxLinesTextInputFormatter(4)],
)
Crazy Lazy Cat
  • 13,595
  • 4
  • 30
  • 54
2

You can use allMatches() function to count the number of lines the input contains and update an error variable if the function returns 4 or more.

if (('\n'.allMatches(text).length + 1) > 4) { // check for new lines and update bool variable }

An example:

import 'package:flutter/material.dart';

class Demo extends StatefulWidget {
  @override
  _DemoState createState() => _DemoState();
}

class _DemoState extends State<Demo> {
  final textController = TextEditingController();

  bool error = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("DEMO"),
        ),
        body: Container(
            child: Column(children: [
          TextField(
            controller: textController,
            keyboardType: TextInputType.multiline,
            maxLines: 4,
            maxLength: 150,
            onChanged: (text) {
              setState(() {
                if (('\n'.allMatches(text).length + 1) > 4) {
                  error = true;
                } else {
                  error = false;
                }
              });
            },
          ),
          error ? Text("More than 4 lines entered") : Container()
        ])));
  }
}
OMi Shah
  • 5,768
  • 3
  • 25
  • 34
1

I updated the Crazy Lazy Cat answer to null safety

import 'dart:math';

import 'package:flutter/services.dart';

class MaxLinesTextInputFormatter extends TextInputFormatter {
  MaxLinesTextInputFormatter(this._maxLines) : assert(_maxLines == -1 || _maxLines > 0);

  final int _maxLines;

  @override
  TextEditingValue formatEditUpdate(
    TextEditingValue oldValue, // unused.
    TextEditingValue newValue,
  ) {
    if (_maxLines > 0) {
      final regEx = RegExp("^.*((\n?.*){0,${_maxLines - 1}})");
      final newString = regEx.stringMatch(newValue.text) ?? "";
      final maxLength = newString.length;
      if (newValue.text.runes.length > maxLength) {
        final newSelection = newValue.selection.copyWith(
          baseOffset: min(newValue.selection.start, maxLength),
          extentOffset: min(newValue.selection.end, maxLength),
        );
        final iterator = RuneIterator(newValue.text);
        if (iterator.moveNext()) {
          for (var count = 0; count < maxLength; ++count) {
            if (!iterator.moveNext()) break;
          }
        }
        final truncated = newValue.text.substring(0, iterator.rawIndex);
        return TextEditingValue(
          text: truncated,
          selection: newSelection,
          composing: TextRange.empty,
        );
      }
      return newValue;
    }
    return newValue;
  }
}
PoQ
  • 304
  • 3
  • 15