WWDC23 SALE: Save 50% on all my Swift books and bundles! >>

UIButtonBarButton layout warning on entering UITextView on iPad not on iPhone

Forums > iOS

I have a simple feedback form, which is one of two ViewControllers in a TabBarController. No storyboards, all programmatic UI (UIKit), Universal app. When entering the textView on iPads, the console throws a layout warning:

    "<NSAutoresizingMaskLayoutConstraint:0x2808aa5d0 h=--& v=--& _UIButtonBarButton:0x129e8c6d0.height == 0   (active)>",
    "<NSLayoutConstraint:0x2808a9d60 V:|-(6)-[_UIUCBKBSelectionBackground:0x129e8d4e0]   (active, names: '|':_UIButtonBarButton:0x129e8c6d0 )>",
    "<NSLayoutConstraint:0x2808a9e00 _UIUCBKBSelectionBackground:0x129e8d4e0.bottom == _UIButtonBarButton:0x129e8c6d0.bottom - 6   (active)>"

This does not happen on iPhones. Other than this the form works fine. I have no idea where this comes from and how to solve it. Any tips appreciated. UPDATE; this now shows on all my textViews en TextFields upon entering. IOS14.2 thing ?

ViewController code:

import UIKit
import MessageUI

struct MailOption {
    let url: URL!
    let titel: String
}

class FeedbackViewController: UIViewController, MFMailComposeViewControllerDelegate ,UINavigationControllerDelegate {
    //MARK: - Properties
    let logoImageView = ProjectImageView(frame: .zero)
    let appVersionLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .title3), andColor: .label, numberOfLines: 2)
    let developerLabel = ProjectTitleLabel(withTextAlignment: .center, andFont: UIFont.preferredFont(forTextStyle: .footnote), andColor: .tertiarySystemBackground, numberOfLines: 1)
    let feedbackTextView = ProjectTextView()
    let spacerView = UIView()
    let sendButton = ProjectButton(withBackgroundColor: .systemOrange, andTitle: "Send")

    var feedbackTextViewPlaceholder = ProjectTextViewPlaceholderTextFor.feedback

    let emailTo = ProjectEmailAddresses.g
    var emailBody = ""
    let emailSubject = "Feedback on \(Bundle.main.displayName ?? "Unknown"), version \(Bundle.main.version ?? "")"

    //MARK: - ViewController Methods
    override func viewDidLoad() {
        super.viewDidLoad()

        configureViewController()
        configureView()
        configureAppVersionLabel()
        configureFeedbackTextView()
        configureSendButton()
    }

    //MARK: - Actions
    @objc private func endEditing() {
        view.endEditing(true)
    }

    @objc func adjustForKeyboard(notification: Notification) {
        //Reset to origin position
        self.view.frame.origin.y = 0

        if notification.name.rawValue == "UIKeyboardWillChangeFrameNotification" {
            if feedbackTextView.isFirstResponder {
                self.view.frame.origin.y -= feedbackTextView.frame.origin.y + 10
            } 
        } else {
            self.view.frame.origin.y = 0
        }
    }

    @objc private func sendButtonTapped() {
        //Extra check on feedback being present
        guard let feedback = feedbackTextView.text, feedback.isNotEmpty else { return }

        emailBody = feedback
        let subjectEncoded = emailSubject.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
        let bodyEncoded = emailBody.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!

        let gMail = MailOption(url: URL(string: "googlegmail://co?to=\(emailTo)&subject=\(subjectEncoded)&body=\(bodyEncoded)"), titel: "GMail")
        let outlook = MailOption(url: URL(string: "ms-outlook://compose?to=\(emailTo)&subject=\(subjectEncoded)&body=\(bodyEncoded)"), titel: "Outlook")
        let spark = MailOption(url: URL(string: "readdle-spark://compose?recipient=\(emailTo)&subject=\(subjectEncoded)&body=\(bodyEncoded)"), titel: "Spark")

        let availableUrls = [gMail, outlook, spark].filter { (mailOption) -> Bool in
            return mailOption.url != nil && UIApplication.shared.canOpenURL(mailOption.url)
        }

        showEmailOptions(for: availableUrls)
    }

    //MARK: - Mail functionality
    func showEmailOptions(for mailOptions: [MailOption]) {
        //Create action sheet
        let ac = UIAlertController(title: "Choose your preferred method of sending mail", message: nil, preferredStyle: .actionSheet)

        //Add Apple Mail action if that is available
        if MFMailComposeViewController.canSendMail() {
            ac.addAction(UIAlertAction(title: "Apple Mail", style: .default, handler: { (_) in
                self.sendAppleMail()
            }))
        }

        //Add other available actions
        for option in mailOptions {
            ac.addAction(UIAlertAction(title: option.titel, style: .default, handler: { (_) in
                UIApplication.shared.open(option.url, options: [:], completionHandler: { _ in
                    self.showSuccess()
                })
            }))
        }

        //Cancel action
        ac.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))

        //Needed for iPads
        if UIDevice.current.userInterfaceIdiom == .pad {
            ac.popoverPresentationController?.sourceView = sendButton
            ac.popoverPresentationController?.permittedArrowDirections = [.down, .up]
        }

        present(ac, animated: true)
    }

    func sendAppleMail() {
        //we already checked for availability of the Mail App!
        let composer = MFMailComposeViewController()

        composer.mailComposeDelegate = self
        composer.setToRecipients([emailTo])
        composer.setSubject(emailSubject)
        composer.setMessageBody(emailBody, isHTML: false)

        present(composer, animated: true)
    }

    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
        if result == .sent {
            self.showSuccess()
        }

        controller.dismiss(animated: true)
    }

    private func showSuccess() {
        self.presentProjectAlertOnMainThread(withTitle: "Feedback sent!", andMessage: "Thank you for your input!", andDismissButtonTitle: "OK", andConfirmButtonTitle: nil, completion: { _ in
            self.feedbackTextView.text = ""
            self.sendButton.isEnabled = false
        })
    }

    //MARK: - UI & Layout
    private func configureViewController() {
        feedbackTextView.delegate = self

        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(endEditing)))

        let notificationCenter = NotificationCenter.default
        notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
        notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
    }

    private func configureView() {
        let padding: CGFloat = 20

        logoImageView.layer.cornerRadius = 5
        logoImageView.contentMode = .scaleAspectFit

        appVersionLabel.setContentHuggingPriority(.required, for: .vertical)
        developerLabel.setContentHuggingPriority(.required, for: .vertical)

        spacerView.translatesAutoresizingMaskIntoConstraints = false

        let seperator = Separator(frame: .zero)

        let stackView = UIStackView(arrangedSubviews: [logoImageView, appVersionLabel, developerLabel, seperator, feedbackTextView, spacerView, sendButton])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .vertical
        stackView.alignment = .center
        stackView.spacing = 10
        stackView.distribution = .fill

        view.addSubview(stackView)

        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: padding),
            stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -padding),

            logoImageView.heightAnchor.constraint(equalToConstant: 88),
            logoImageView.widthAnchor.constraint(equalTo: logoImageView.heightAnchor),
            logoImageView.centerXAnchor.constraint(equalTo: stackView.centerXAnchor),

            seperator.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: padding),
            seperator.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -padding),

            feedbackTextView.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(2 * padding)),
            feedbackTextView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.33),

            spacerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 22),
            spacerView.widthAnchor.constraint(equalTo: stackView.widthAnchor),

            sendButton.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(2 * padding)),
            sendButton.heightAnchor.constraint(equalToConstant: 44)
        ])

    }

    private func configureAppVersionLabel() {
        guard let appName = Bundle.main.displayName, let appVersion = Bundle.main.version else {
            appVersionLabel.text = "Unknown"
            return
        }
        appVersionLabel.text = "\(appName)\n\(appVersion)"

        developerLabel.text = "developer"
    }

    @objc private func configureFeedbackTextView() {
        feedbackTextView.text = feedbackTextViewPlaceholder
        feedbackTextView.textColor = feedbackTextView.text == ProjectTextViewPlaceholderTextFor.feedback ? .tertiaryLabel : .label
    }

    private func configureSendButton() {
        sendButton.addTarget(self, action: #selector(sendButtonTapped), for: .touchUpInside)
        sendButton.isEnabled = false
    }
}

//MARK: - Delegate UITextView
extension FeedbackViewController: UITextViewDelegate {
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        //2000 chars restriction
        return textView.text.count + (text.count - range.length) <= 2000
    }

    func textViewDidBeginEditing(_ textView: UITextView) {
        if textView.textColor == UIColor.tertiaryLabel {
            textView.text = ""
            textView.textColor = .label
        }
    }

    func textViewDidEndEditing(_ textView: UITextView) {
        if textView.text.isEmpty {
            textView.text = ProjectTextViewPlaceholderTextFor.feedback
            textView.textColor = .tertiaryLabel
            feedbackTextViewPlaceholder = ""
        } else {
            feedbackTextViewPlaceholder = textView.text
        }
    }

    func textViewDidChange(_ textView: UITextView) {
        feedbackTextViewPlaceholder = textView.text

        if let text = textView.text, text.isNotEmpty, text != ProjectTextViewPlaceholderTextFor.feedback {
            sendButton.isEnabled = true
        } else {
            sendButton.isEnabled = false
        }
    }
}

1      

I'm having similar problem. Anyone find a solution?

1      

I didn't get any help anywhere. Not even on the Apple forums. I've shipped my app with this error still present .... So, not ideally, but just ignore the error ...

1      

Thank You Sir!

1      

Save 50% in my WWDC23 sale.

SAVE 50% To celebrate WWDC23, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.