Разверните и сверните строку UITableView в соответствии с динамически изменяемым параметром UITextView, нажав кнопку

#ios #swift

#iOS #swift

Вопрос:

Я реализовал UITableView. У меня есть UITextView и UIButton в представлении содержимого ячейки табличного представления. Прокрутка текстового представления включена, и задано ограничение по высоте. Высота строки табличного представления автоматически зависит от высоты текстового представления плюс высоты кнопки.

Теперь я хочу реализовать нажатие кнопки переключения свойства isScrollEnabled текстового представления и удалить ограничение высоты. При повторном нажатии кнопки переключите свойство isScrollEnabled в текстовом представлении и добавьте ограничение высоты.

В этом процессе текстовое представление автоматически не меняет свой фрейм (аналогичная вещь может быть достигнута с помощью UILabel вместо UITextView и переключением свойства number of lines метки вместо text view с включенной прокруткой) при нажатии кнопки, и, следовательно, высота строки также не обновляется.

Ожидаемый результат при первом нажатии кнопки, устанавливающей свойство isScrollEnabled для текстового представления в значение false, и удаление ограничения высоты текстового представления приведет к расширению текстового представления в соответствии с содержащимся в нем текстом. При повторном нажатии кнопки установка свойства isScrollEnabled для текстового представления в значение true и добавление ограничения высоты текстового представления приведет к свертыванию текстового представления до постоянного значения ограничения высоты. И, соответственно, высота строки табличного представления также изменится, поскольку я использую автозапуск.

Комментарии:

1. Вы разрабатываете свою ячейку с помощью кода или прототипа раскадровки? Какова высота вашего текстового представления? И вы хотите, чтобы это была «минимальная» высота при отключенной прокрутке? Например, если ваша высота «прокрутки» равна 60, хотите ли вы, чтобы она все еще была 60, если есть только одна строка текста?

2. Я использую раскадровку для оформления ячеек. Изначально я добавил ограничение высоты для текстового представления со значением 64, и прокрутка включена. При нажатии кнопки я отключаю прокрутку и удаляю ограничение высоты. Это должно привести к тому, что высота текстового представления будет соответствовать тексту в нем (без минимальной высоты), а из-за автоматической компоновки высота строки должна автоматически корректироваться в соответствии с новой высотой текстового представления. Но при нажатии кнопки текстовое представление не меняет свою высоту.

Ответ №1:

A UITableView автоматически не пересчитывает высоту строки при динамическом изменении высоты ячейки, например, при изменении ограничения или изменении внутренней высоты элемента пользовательского интерфейса (например, при вводе текста в текстовом представлении).

Итак, ячейка должна сообщить контроллеру, что ее содержимое изменилось.

Предположительно, вы используете шаблон протокола / делегирования или замыкание для обновления ваших данных, когда пользователь вводит текстовое представление? Если это так, именно здесь мы можем сообщить контроллеру и позволить ему обновить высоту (высоты) строки. (Если вы еще этого не сделали, вам необходимо это реализовать.)

Самый простой способ обновить таблицу без перезагрузки данных (чего мы не хотим делать) — вызвать:

 tableView.performBatchUpdates(nil, completion: nil)
  

Если мы уже используем замыкание для обновления данных по мере ввода пользователем, мы можем выполнить этот вызов одновременно … и мы можем включить состояние «Развернуто / свернуто» (чтобы мы могли отслеживать это в нашем источнике данных), и мы можем использовать то же замыкание, когда пользователь вводит данные.пользователь нажимает кнопку, чтобы переключить прокрутку в текстовом представлении.

Вот как я расположил свою ячейку с ее ограничениями:

введите описание изображения здесь

Это Scrolling Height Constraint 64 и есть ограничение, которое мы собираемся переключать между активным / неактивным. Обратите внимание, что для текстового поля существует второе ограничение высоты: height >= 64 . Это позволяет сохранить текстовое представление на его «минимальной высоте» при редактировании. Без этого, если мы удалим достаточное количество текста, чтобы у нас была только одна или две строки, вместо этого:

введите описание изображения здесь

в итоге мы получим ячейку, которая выглядит следующим образом:

введите описание изображения здесь

Вот несколько примеров кода…

Во-первых, простая структура для наших данных:

 struct MyDataItem {
    var text: String = ""
    var isScrollEnabled: Bool = false
}
  

В нашем классе cell будет объявлено закрытие, которое мы будем использовать для информирования контроллера о появлении изменений:

 var callback: ((String, Bool) -> ())?
  

Далее, пример контроллера просмотра прокрутки, который реализует это закрытие в cellForRowAt :

 class ExpandTableViewController: UITableViewController {
    
    var myData: [MyDataItem] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // start with 20 sample strings (so we have table view scrolling)
        for i in 0..<20 {
            myData.append(MyDataItem(text: "Sample string (i)", isScrollEnabled: true))
        }
        
        // start with 5 lines of text in Second row
        myData[1].text = "Sample string 1nLine 2nLine 3nLine 4nLine 5"
        
        // start with long text in Fourth row (for word wrapping)
        myData[3].text = "Sample string 3 - with plenty of text to demonstrate word wrapping in the text view, and how it will affect the scrolling and expand / collapse feature of the cell."
        
        // add a right nav bar "Done" button to stop editing and dismiss the keyboard
        let btn = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(self.doneTapped))
        self.navigationItem.rightBarButtonItem = btn
    }
    
    // to dismiss keyboard
    @objc func doneTapped() -> Void {
        view.endEditing(true)
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myData.count
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "textViewCell", for: indexPath) as! TextViewCell
        
        let item = myData[indexPath.row]
        
        cell.fillData(item)
        
        cell.callback = { [weak self] str, scroll in
            guard let self = self else { return }
            
            // update our data
            self.myData[indexPath.row].text = str
            self.myData[indexPath.row].isScrollEnabled = scroll
            
            // tell the table view to re-layout cells where needed
            //  this will change row heights when editing and/or when
            //  tapping Expand/Collapse button
            self.tableView.performBatchUpdates(nil, completion: nil)
        }
        
        return cell
    }
    
}
  

Вот класс cell, где происходит большая часть «волшебства» 🙂 … Я включил логику для переключения .isEnabled кнопки «Развернуть / свернуть» в зависимости от объема текста в текстовом представлении:

 class TextViewCell: UITableViewCell, UITextViewDelegate {
    
    @IBOutlet var theTextView: UITextView!
    @IBOutlet var theButton: UIButton!
    
    @IBOutlet var scrollingHeightConstraint: NSLayoutConstraint!

    var callback: ((String, Bool) -> ())?
    
    func textViewDidChange(_ textView: UITextView) {
        
        let t = textView.text ?? ""
        
        // inform the controller that our text changed
        callback?(t, textView.isScrollEnabled)
        
        updateTheButton()

    }
    
    @IBAction func didTap(_ sender: Any) {

        // toggle scrolling on the text view
        theTextView.isScrollEnabled.toggle()

        updateTheConstrints()
        updateTheButton()

        let t = theTextView.text ?? ""

        // inform the controller the expand / collapse button was tapped
        callback?(t, theTextView.isScrollEnabled)

        // if we're editing and just tapped Collapse
        if theTextView.isFirstResponder amp;amp; theTextView.isScrollEnabled {
            //scroll the text view so the Cursor is visible *after* the view has resized
            DispatchQueue.main.async {
                self.scrollToCursorPosition()
            }
        }
    }
    
    private func scrollToCursorPosition() {
        if let r = theTextView.selectedTextRange?.start {
            let c = theTextView.caretRect(for: r)
            theTextView.scrollRectToVisible(c, animated: true)
        }
    }
    
    func updateTheButton() -> Void {
        
        // set button title appropriately
        theButton.setTitle(theTextView.isScrollEnabled ? "Expand" : "Collapse", for: [])

        DispatchQueue.main.async {
            // enable / disablbe button based on amount of text
            //  have to do this async, so it runs after the text has changed
            self.theButton.isEnabled = self.theTextView.contentSize.height > self.scrollingHeightConstraint.constant
        }
        
    }
    
    func updateTheConstrints() -> Void {
        // activate or deactivate text view's height constraint
        scrollingHeightConstraint.isActive = theTextView.isScrollEnabled
    }
    
    func fillData(_ item: MyDataItem) -> Void {
        theTextView.text = item.text
        theTextView.isScrollEnabled = item.isScrollEnabled
        updateTheConstrints()
        updateTheButton()
    }
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    override func awakeFromNib() {
        super.awakeFromNib()
        commonInit()
    }
    func commonInit() -> Void {
        if theTextView != nil {
            // mke sure the text view's delegate is set
            theTextView.delegate = self
            
            // anything else we may want to do on init
        }
    }
}
  

и, наконец, источник для раскадровки, которую я использую:

 <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="VjW-oA-FRf">
    <device id="retina4_7" orientation="portrait" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
        <capability name="System colors in document resources" minToolsVersion="11.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--Expand Table View Controller-->
        <scene sceneID="PM6-ph-sPi">
            <objects>
                <tableViewController id="VjW-oA-FRf" customClass="ExpandTableViewController" customModule="DelMe" customModuleProvider="target" sceneMemberID="viewController">
                    <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="xK8-0I-ylV">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                        <prototypes>
                            <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="textViewCell" rowHeight="101" id="MWI-1y-UOV" customClass="TextViewCell" customModule="DelMe" customModuleProvider="target">
                                <rect key="frame" x="0.0" y="28" width="375" height="101"/>
                                <autoresizingMask key="autoresizingMask"/>
                                <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="MWI-1y-UOV" id="ZYw-rr-lnO">
                                    <rect key="frame" x="0.0" y="0.0" width="375" height="101"/>
                                    <autoresizingMask key="autoresizingMask"/>
                                    <subviews>
                                        <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" text="Lorem ipsum dolor sit er elit lamet." textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="pCr-bW-XWY">
                                            <rect key="frame" x="16" y="11" width="251" height="64"/>
                                            <color key="backgroundColor" red="0.55634254220000001" green="0.97934550050000002" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                            <constraints>
                                                <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="64" id="4SV-lM-57L"/>
                                                <constraint firstAttribute="height" constant="64" id="jBQ-hv-SBY"/>
                                            </constraints>
                                            <color key="textColor" systemColor="labelColor"/>
                                            <fontDescription key="fontDescription" type="system" pointSize="16"/>
                                            <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
                                        </textView>
                                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Bex-xS-WMG">
                                            <rect key="frame" x="279" y="11" width="80" height="30"/>
                                            <constraints>
                                                <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="80" id="CG1-AO-HEE"/>
                                            </constraints>
                                            <state key="normal" title="Expand"/>
                                            <connections>
                                                <action selector="didTap:" destination="MWI-1y-UOV" eventType="touchUpInside" id="KlW-my-Tna"/>
                                            </connections>
                                        </button>
                                    </subviews>
                                    <constraints>
                                        <constraint firstItem="Bex-xS-WMG" firstAttribute="top" secondItem="ZYw-rr-lnO" secondAttribute="topMargin" id="3de-fi-JG9"/>
                                        <constraint firstItem="pCr-bW-XWY" firstAttribute="top" secondItem="ZYw-rr-lnO" secondAttribute="topMargin" id="Xuc-OZ-KQh"/>
                                        <constraint firstItem="Bex-xS-WMG" firstAttribute="trailing" secondItem="ZYw-rr-lnO" secondAttribute="trailingMargin" id="Ypd-BU-eNg"/>
                                        <constraint firstItem="Bex-xS-WMG" firstAttribute="leading" secondItem="pCr-bW-XWY" secondAttribute="trailing" constant="12" id="ihY-xL-anh"/>
                                        <constraint firstAttribute="bottomMargin" relation="greaterThanOrEqual" secondItem="pCr-bW-XWY" secondAttribute="bottom" id="iu3-Sw-yey"/>
                                        <constraint firstItem="pCr-bW-XWY" firstAttribute="leading" secondItem="ZYw-rr-lnO" secondAttribute="leadingMargin" id="o85-kl-ee4"/>
                                    </constraints>
                                </tableViewCellContentView>
                                <connections>
                                    <outlet property="scrollingHeightConstraint" destination="jBQ-hv-SBY" id="AQQ-LK-f5R"/>
                                    <outlet property="theButton" destination="Bex-xS-WMG" id="Dpg-wY-06G"/>
                                    <outlet property="theTextView" destination="pCr-bW-XWY" id="vzw-GP-Eav"/>
                                </connections>
                            </tableViewCell>
                        </prototypes>
                        <connections>
                            <outlet property="dataSource" destination="VjW-oA-FRf" id="g9q-N9-GPB"/>
                            <outlet property="delegate" destination="VjW-oA-FRf" id="9Ff-xj-zPJ"/>
                        </connections>
                    </tableView>
                    <navigationItem key="navigationItem" id="ztb-ky-WPU"/>
                </tableViewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="5rS-VR-5Ht" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="-124" y="1613"/>
        </scene>
    </scenes>
    <resources>
        <systemColor name="labelColor">
            <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
        <systemColor name="systemBackgroundColor">
            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
    </resources>
</document>
  

Пример вывода:

введите описание изображения здесь