【问题标题】:update tableView when cell's element's frame changes当单元格元素的框架发生变化时更新 tableView
【发布时间】:2016-07-22 10:26:20
【问题描述】:

我正在尝试添加 see more 功能,例如 this 。在我的UITextView 上,它位于tableView's cell 内,为此我正在使用这个类,它基本上是 TableView 的一个子类,带有一个按钮,可以将 TextView 扩展到所需的高度:

    @IBDesignable
class ReadMoreTextView: UITextView {

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        scrollEnabled = false
        editable = false
    }



    convenience init(frame: CGRect) {
        self.init(frame: frame, textContainer: nil)
    }

    convenience init() {
        self.init(frame: CGRectZero, textContainer: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        scrollEnabled = false
        editable = false
    }

    convenience init(maximumNumberOfLines: Int, trimText: NSString?, shouldTrim: Bool) {
        self.init()
        self.maximumNumberOfLines = maximumNumberOfLines
        self.trimText = trimText
        self.shouldTrim = shouldTrim
    }

    convenience init(maximumNumberOfLines: Int, attributedTrimText: NSAttributedString?, shouldTrim: Bool) {
        self.init()
        self.maximumNumberOfLines = maximumNumberOfLines
        self.attributedTrimText = attributedTrimText
        self.shouldTrim = shouldTrim
    }

    @IBInspectable
    var maximumNumberOfLines: Int = 0 {
        didSet { setNeedsLayout() }
    }



    @IBInspectable
    var trimText: NSString? {
        didSet { setNeedsLayout() }
    }

    var attributedTrimText: NSAttributedString? {
        didSet { setNeedsLayout() }
    }

    @IBInspectable
    var shouldTrim: Bool = false {
        didSet { setNeedsLayout() }
    }

    var trimTextRangePadding: UIEdgeInsets = UIEdgeInsetsZero
    var appendTrimTextPrefix: Bool = true
    var trimTextPrefix: String = "..."

    private var originalText: String!

    override var text: String! {
        didSet {
            originalText = text
            originalAttributedText = nil
            if needsTrim() { updateText() }
        }
    }

    private var originalAttributedText: NSAttributedString!

    override var attributedText: NSAttributedString! {
        didSet {
            originalAttributedText = attributedText
            originalText = nil
            if needsTrim() { updateText() }
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        needsTrim() ? updateText() : resetText()

    }

    func needsTrim() -> Bool {
        return shouldTrim && _trimText != nil
    }

    func updateText() {
        textContainer.maximumNumberOfLines = maximumNumberOfLines
        textContainer.size = CGSizeMake(bounds.size.width, CGFloat.max)

        let range = rangeToReplaceWithTrimText()
        if range.location != NSNotFound {
            let prefix = appendTrimTextPrefix ? trimTextPrefix : ""

            if let text = trimText?.mutableCopy() as? NSMutableString {
                text.insertString("\(prefix) ", atIndex: 0)
                textStorage.replaceCharactersInRange(range, withString: text as String)
            }
            else if let text = attributedTrimText?.mutableCopy() as? NSMutableAttributedString {
                text.insertAttributedString(NSAttributedString(string: "\(prefix) "), atIndex: 0)
                textStorage.replaceCharactersInRange(range, withAttributedString: text)
            }
        }
        invalidateIntrinsicContentSize()


    }

    func resetText() {
        textContainer.maximumNumberOfLines = 0
        if originalText != nil {
            textStorage.replaceCharactersInRange(NSMakeRange(0, countElements(text!)), withString: originalText)

            print("Trim Pressed resetText")

        }
        else if originalAttributedText != nil {
            textStorage.replaceCharactersInRange(NSMakeRange(0, countElements(text!)), withAttributedString: originalAttributedText)
        }
        invalidateIntrinsicContentSize()

         // maybe this is what we're looking for



    }

    override func intrinsicContentSize() -> CGSize {
        textContainer.size = CGSizeMake(bounds.size.width, CGFloat.max)
        var intrinsicContentSize = layoutManager.boundingRectForGlyphRange(layoutManager.glyphRangeForTextContainer(textContainer), inTextContainer: textContainer).size
        intrinsicContentSize.width = UIViewNoIntrinsicMetric
        intrinsicContentSize.height += (textContainerInset.top + textContainerInset.bottom)
        return intrinsicContentSize


    }

    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

        if needsTrim() && pointInTrimTextRange(point) {
            shouldTrim = false
            maximumNumberOfLines = 0
        }

        return super.hitTest(point, withEvent: event)

    }

    //MARK: Private methods

    private var _trimText: NSString? {
        get {
            return trimText ?? attributedTrimText?.string
        }
    }

    private var _trimTextPrefixLength: Int {
        get {
            return appendTrimTextPrefix ? countElements(trimTextPrefix) + 1 : 1
        }
    }

    private var _originalTextLength: Int {
        get {
            if originalText != nil {
                return countElements(originalText!)
            }
            else  if originalAttributedText != nil {
                return originalAttributedText!.length
            }
            return 0
        }
    }

    private func rangeToReplaceWithTrimText() -> NSRange {
        let emptyRange = NSMakeRange(NSNotFound, 0)

        var rangeToReplace = layoutManager.characterRangeThatFits(textContainer)
        if NSMaxRange(rangeToReplace) == _originalTextLength {
            rangeToReplace = emptyRange
        }
        else {
            rangeToReplace.location = NSMaxRange(rangeToReplace) - _trimText!.length - _trimTextPrefixLength
            if rangeToReplace.location < 0 {
                rangeToReplace = emptyRange
            }
            else {
                rangeToReplace.length = textStorage.length - rangeToReplace.location
            }
        }
        return rangeToReplace
    }

    private func trimTextRange() -> NSRange {
        var trimTextRange = rangeToReplaceWithTrimText()
        if trimTextRange.location != NSNotFound {
            trimTextRange.length = _trimTextPrefixLength + _trimText!.length
        }
        return trimTextRange
    }

    private func pointInTrimTextRange(point: CGPoint) -> Bool {
        let offset = CGPointMake(textContainerInset.left, textContainerInset.top)
        var boundingRect = layoutManager.boundingRectForCharacterRange(trimTextRange(), inTextContainer: textContainer, textContainerOffset: offset)
        boundingRect = CGRectOffset(boundingRect, textContainerInset.left, textContainerInset.top)
        boundingRect = CGRectInset(boundingRect, -(trimTextRangePadding.left + trimTextRangePadding.right), -(trimTextRangePadding.top + trimTextRangePadding.bottom))
        return CGRectContainsPoint(boundingRect, point)
    }

    func countElements(text: String) -> Int {
        return text.characters.count
    }
}

//MARK: NSLayoutManager extension

extension NSLayoutManager {

    func characterRangeThatFits(textContainer: NSTextContainer) -> NSRange {
        var rangeThatFits = self.glyphRangeForTextContainer(textContainer)
        rangeThatFits = self.characterRangeForGlyphRange(rangeThatFits, actualGlyphRange: nil)
        return rangeThatFits
    }

    func boundingRectForCharacterRange(range: NSRange, inTextContainer textContainer: NSTextContainer, textContainerOffset: CGPoint) -> CGRect {
        let glyphRange = self.glyphRangeForCharacterRange(range, actualCharacterRange: nil)
        let boundingRect = self.boundingRectForGlyphRange(glyphRange, inTextContainer: textContainer)
        return boundingRect
    }

}

如果我的 textView 在 ViewController 中,上面的工作正常,但由于我在 UITableViewController 中的单元格内有这个,我无法用新 TextView 的高度(更新的 textView )更新单元格的高度想法当textView的高度更新时如何更新我的tableView?

附:我知道我必须使用tableView.beginUpdateendUpdate,但我问什么时候使用这个?如何知道textView的frame是否改变了

【问题讨论】:

  • 您是否尝试过将您的tableview 的属性RequiredHeight(我不确定它的确切名称,但在智能感知中查找带有height 的任何内容)设置为UITableView.AutomaticDimension?跨度>
  • 我建议使用以下库:- github.com/Ramotion/folding-cell
  • 我只想在用户点击单元格时展开单元格@Zil 这就是为什么设置 AutomaticDimension 对我不起作用

标签: ios uitableview swift2 uitextview


【解决方案1】:

第一:我没有从您的表中看到 datasourcedelegate 方法。

您的对象中需要有 3 个键。

"heightCell" : 53, "originalHeight" : 0, "isexpanded" : 0

你需要在cellForRowAtIndexPath

let eachRow = faqInfo.objectAtIndex(indexPath.row)
eachRow.setValue(53, forKey: "heightCell")

var currentHeght = eachRow["heightCell"] as! CGFloat
//25 default question height
eachRow.setValue(currentHeght + (newQuestionHeight - 25), forKey: "heightCell")

var currentHeght = eachRow["heightCell"] as! CGFloat
eachRow.setValue(currentHeght, forKey: "originalHeight")

let isExpanded = eachRow["isexpanded"] as! Bool
if isExpanded == true {
    increaseTextViewInCell(cell, eachRow: eachRow)
}

Function 用于增加单元格中的文本视图

func increaseTextViewInCell(cell: FAQTableViewCellController, eachRow: AnyObject) {
        let answer = eachRow["answer"] as? String
        let newAnswerHeight = Utils.heightForView(answer!, font: UIFont(name: "AvenirNextCondensed-Regular", size: CGFloat(15))!, width: cell.textviewAnswer.frame.size.width, xpos: cell.textviewAnswer.frame.origin.x)

        //1 default question height
        let currentHeght = eachRow["heightCell"] as! CGFloat
        eachRow.setValue(currentHeght + (newAnswerHeight - 1) + 40, forKey: "heightCell")
        cell.textviewAnswer.text = answer
    }

didSelectRowAtIndexPath

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let cell = tableView.cellForRowAtIndexPath(indexPath) as! FAQTableViewCellController
    let eachRow = faqInfo.objectAtIndex(indexPath.row)

    let isExpanded = eachRow["isexpanded"] as! Bool
    if isExpanded == false {

        increaseTextViewInCell(cell, eachRow: eachRow)
        /* unncoment if need unexpand all the others cell
        var i = 0
        for eachInfo in faqInfo {
            let isExpanded = eachInfo["isexpanded"] as! Bool
            if isExpanded == true {
                eachInfo.setValue(0, forKey: "isexpanded")
                tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: i, inSection: 0)], withRowAnimation: .Fade)
                break
            }
            i += 1
        }
        */
        eachRow.setValue(1, forKey: "isexpanded")

    } else {
        let originalHeight = eachRow["originalHeight"] as! CGFloat
        eachRow.setValue(originalHeight, forKey: "heightCell")

        eachRow.setValue(0, forKey: "isexpanded")
    }

    tableView.beginUpdates()
    tableView.endUpdates()
}

还有heightForView 函数

class func heightForView(text:String, font:UIFont, width:CGFloat, xpos:CGFloat) -> CGFloat {
        let label:UILabel = UILabel(frame: CGRectMake(xpos, 0, width, CGFloat.max))
        label.numberOfLines = 0
        label.lineBreakMode = NSLineBreakMode.ByWordWrapping
        label.font = font
        label.text = text

        label.sizeToFit()
        return label.frame.height
    }

【讨论】:

    【解决方案2】:

    我已经使用this code of Ilya Puchka 解决了 TableViewCell 的 TextView 中的 Read More。

    更新

    也许你可以使用 tableView.heightForRowAtIndexPath() 并且 intrinsicContentSize 在展开后会改变

    override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
         // an here use intrinsicContentSize
         return self.intrinsicContentSize().height
    }
    

    未测试!仅已调试!已经够晚了;-)

    【讨论】:

    • 好吧,我使用的是相同的,但是你如何通知 tableview 更新它的行高??
    • 嗯,抱歉,不知道 :-(
    猜你喜欢
    • 1970-01-01
    • 2018-04-06
    • 2017-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多