iosswiftuitableviewuiscrollviewinputaccessoryview

InputAccessoryView bug when dismiss controller and return ago


Who can help me? I create chat controller via UITableViewController, UINavigationController and I use InputAccessoryView. If I swipe screen to left (dismiss this controller) and return swipe to right (cancel dismiss) - table set adjustContentInset bottom to zero and InputAcessoryView close bottom content tableView. This problem created in ViewWillAppear event. My code:

UITableViewController:

// MARK: - Controller data

lazy var inputContainerView = ChatAccessoryView(frame: .zero, buttonSelector: #selector(sendMessage(sender:)), controller: self)

public var ticketID: Int = 0

private var lastMessageID: Int = 0
private var tableSections: [String] = []
private var tableRows: [[SupportTicketMessage]] = []
private var sendingMessage: Bool = false
private var URLTaskGetMessages: URLSessionDataTask?
private var URLTaskSendMessage: URLSessionDataTask?

// MARK: - Controller overrides

override func loadView() {
    super.loadView()

    tableView.register(ChatHeaderView.self, forHeaderFooterViewReuseIdentifier: "chatHeader")
    tableView.register(ChatFromMessageTableViewCell.self, forCellReuseIdentifier: "chatFromMessage")
    tableView.register(ChatToMessageTableViewCell.self, forCellReuseIdentifier: "chatToMessage")

    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)

    if let messages = supportClass.getTicketMessages(ticketID) {
        tableSections = messages.sections
        tableRows = messages.rows
        lastMessageID = messages.lastMessage

        scrollTableToBottom(false)
    } else {
        tableView.setLoaderBackground("Загрузка сообщений...")
    }
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    loadMessages()
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)

    URLTaskGetMessages?.cancel()
    URLTaskSendMessage?.cancel()
}

override var inputAccessoryView: UIView? {
    return inputContainerView
}

override var canBecomeFirstResponder: Bool {
    return true
}

ChatAccessoryView:

class ChatAccessoryView: UIView {

// MARK: - Get params to init

let buttonSelector: Selector
let controller: UIViewController

// MARK: - Data

private let textView = ChatTextView()
private let sendButton = LoadingButton(frame: .zero, text: "Отпр.")
private let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .extraLight))

// MARK: - Init

required init(frame: CGRect, buttonSelector: Selector, controller: UIViewController) {
    self.buttonSelector = buttonSelector
    self.controller = controller

    super.init(frame: frame)

    configureContents()
    blurEffectConfigure()
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

// MARK: - Overrides

override var intrinsicContentSize: CGSize {
    return .zero
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    blurEffectConfigure()
}

override func didMoveToWindow() {
    super.didMoveToWindow()

    if let window = window {
        textView.bottomAnchor.constraint(lessThanOrEqualToSystemSpacingBelow: window.safeAreaLayoutGuide.bottomAnchor, multiplier: 1.0).isActive = true
    }
}

// MARK: - Private methods

private func configureContents() {
    backgroundColor = .clear
    autoresizingMask = .flexibleHeight

    textView.placeholder = "Напишите сообщение..."
    textView.maxHeight = 160

    textView.font = .systemFont(ofSize: 17)
    textView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    textView.layer.cornerRadius = 8
    textView.textContainerInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 4)

    sendButton.titleLabel?.font = .boldSystemFont(ofSize: 17)
    sendButton.setTitleColor(controller.view.tintColor, for: .normal)
    sendButton.addTarget(controller, action: buttonSelector, for: .touchUpInside)

    blurView.translatesAutoresizingMaskIntoConstraints = false
    textView.translatesAutoresizingMaskIntoConstraints = false
    sendButton.translatesAutoresizingMaskIntoConstraints = false

    self.addSubview(blurView)
    self.addSubview(textView)
    self.addSubview(sendButton)

    blurView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
    blurView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
    blurView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
    blurView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true

    textView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20).isActive = true
    textView.topAnchor.constraint(equalTo: self.topAnchor, constant: 8).isActive = true
    textView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -8 ).isActive = true
    textView.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor, constant: -20).isActive = true

    sendButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
    sendButton.widthAnchor.constraint(equalToConstant: 48).isActive = true
    sendButton.centerYAnchor.constraint(equalTo: textView.centerYAnchor).isActive = true
}

private func blurEffectConfigure() {
    if #available(iOS 13.0, *) {
        if traitCollection.userInterfaceStyle == .light {
            blurView.effect = UIBlurEffect(style: .extraLight)
        } else {
            blurView.effect = UIBlurEffect(style: .dark)
        }
    }
}

// MARK: - Public methods

public func successSend() {
    textView.text = ""
    sendButton.loadingMode(false)
}

}

ChatTextView:

class ChatTextView: UITextView {

// MARK: - Data

var maxHeight: CGFloat = 0.0

public let placeholderTextView: UITextView = {
    let textView = UITextView()

    textView.translatesAutoresizingMaskIntoConstraints = false
    textView.backgroundColor = .clear
    textView.isScrollEnabled = false
    textView.isUserInteractionEnabled = false
    textView.textColor = UIColor.black.withAlphaComponent(0.33)

    return textView
}()

var placeholder: String? {
    get {
        return placeholderTextView.text
    }

    set {
        placeholderTextView.text = newValue
    }
}

// MARK: - Init

override init(frame: CGRect, textContainer: NSTextContainer?) {
    super.init(frame: frame, textContainer: textContainer)

    backgroundColor = UIColor.black.withAlphaComponent(0.06)
    isScrollEnabled = false
    autoresizingMask = [.flexibleWidth, .flexibleHeight]

    NotificationCenter.default.addObserver(self, selector: #selector(UITextInputDelegate.textDidChange(_:)), name: UITextView.textDidChangeNotification, object: self)

    placeholderTextView.font = font

    addSubview(placeholderTextView)

    NSLayoutConstraint.activate([
        placeholderTextView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
        placeholderTextView.trailingAnchor.constraint(equalTo: trailingAnchor),
        placeholderTextView.topAnchor.constraint(equalTo: topAnchor),
        placeholderTextView.bottomAnchor.constraint(equalTo: bottomAnchor),
    ])

    colorThemeConfigure()
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

// MARK: - Overrides

override var text: String! {
    didSet {
        invalidateIntrinsicContentSize()
        placeholderTextView.isHidden = !text.isEmpty
    }
}

override var font: UIFont? {
    didSet {
        placeholderTextView.font = font
        invalidateIntrinsicContentSize()
    }
}

override var contentInset: UIEdgeInsets {
    didSet {
        placeholderTextView.contentInset = contentInset
    }
}

override var intrinsicContentSize: CGSize {
    var size = super.intrinsicContentSize

    if size.height == UIView.noIntrinsicMetric {

        layoutManager.glyphRange(for: textContainer)
        size.height = layoutManager.usedRect(for: textContainer).height + textContainerInset.top + textContainerInset.bottom
    }

    if maxHeight > 0.0 && size.height > maxHeight {
        size.height = maxHeight

        if !isScrollEnabled {
            isScrollEnabled = true
        }
    } else if isScrollEnabled {
        isScrollEnabled = false
    }

    return size
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    colorThemeConfigure()
}

// MARK: - Private methods

private func colorThemeConfigure() {
    if #available(iOS 13.0, *) {
        if traitCollection.userInterfaceStyle == .light {
            backgroundColor = UIColor.black.withAlphaComponent(0.06)
            placeholderTextView.textColor = UIColor.black.withAlphaComponent(0.33)
        } else {
            backgroundColor = UIColor.white.withAlphaComponent(0.08)
            placeholderTextView.textColor = UIColor.white.withAlphaComponent(0.33)
        }
    }
}

// MARK: - Obj C methods

@objc private func textDidChange(_ note: Notification) {
    invalidateIntrinsicContentSize()

    placeholderTextView.isHidden = !text.isEmpty
}

}

Screenshots error:

TableView loaded (all right)

Swipe screen to left and cancel this action (swipe right)

When I returned to controller I see this Bug (last message go to bottom)

Thanks!


Solution

  • Thank you all for your attention and trying to help me! I solved the problem as follows:

        tableView.contentInsetAdjustmentBehavior = .never
        tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 53, right: 0)
    
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
    

    I disable automatic tableView.contentInsetAdjustmentBehavior and use tableView.conentInset. On open/close keyboard I change tableView.contentInset.