UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

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
        }
    }
}

3      

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

3      

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 ...

3      

Thank You Sir!

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

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.