Swaraag
Swaraag

Reputation: 105

How To Create A UITextField With The Ability To Function Like A UIButton?

I am having a problem with creating a nice layout with accomplishing what I want. I want to have a question (I don't need help with this) and 4 answer choices below. I have created these answer choices as Text Fields because that allows me to get a nice border to it that is thin and like I want it to be. But I also want this to be uneditable at the same time clickable. What I mean by this is that the user cannot edit an answer, but when they click on the right answer, the border becomes green or something. I have searched so much, but I always get errors and it never works.

This is an example of the type of layout I want.

I can't make this into a button because it has really ugly and square borders, but I can't do UITextView because I can't make an @IBAction and change specific attributes when clicked. I can't do UITextField because when I click a text field, it doesn't do anything. I can't turn off User Interaction Enabled because then they won't be able to click it. How can I make it work so that it will become like this when clicked but maintains this same format?

(Don't worry about font differences and all.)

I don't have any code to show because there is nothing I have found to work.

Thank you in advance!

Upvotes: 1

Views: 433

Answers (4)

Fattie
Fattie

Reputation: 12582

A beautiful solution is just to simply sit a clear modern UIButton (which has a popup) over the UItextField ...

It's this easy ...

///Beautiful combo of modern popup menu with attractive standard UItextField
class GroovyUITextField: UITextField {
    
    lazy var butter: UIButton = {
        let v = UIButton(type: .custom)
        v.configuration = .borderless()
        v.setTitle("", for: [])
        addSubview(v)
        
        let choices = [
            ("G", "Very ..."),
            ("U", "Custom ..."),
            ("U", "Entry ..."),
            ("U", "Fields ..."),
            ("U", "So sweet ..."),
        ]
        let elements: [UIMenuElement] = choices.map({ c in
            return UIAction(title: c.1) { [weak self] _ in
                guard let self else { return }
                self.didSelectGroovy(tup: c)
            }
        })
        v.menu = UIMenu(children: elements)
        v.isEnabled = true
        v.showsMenuAsPrimaryAction = true
        
        return v
    }()
    
    func didSelectGroovy(tup: (String, String)) {
        text = tup.1 + " custom control under construction"
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        butter.frame = bounds
    }
}

Upvotes: 0

Abuzar Manzoor
Abuzar Manzoor

Reputation: 409

First create outlet for all button, see the image below

Selected image

Image with un selected image

then

@IBAction func languageSelectionButtonPressed(_ sender: UIButton) {
    englishBtn.isSelected = false
    FrenchBtn.isSelected = false
    sender.isSelected = true      
}

Upvotes: 0

Crt Gregoric
Crt Gregoric

Reputation: 402

You could use a plain view, where you can adjust the border and corner radius to make it look how you want it. Put a label inside of it and again set the styling you need/want. To detect touch you could either put a button over it (without any text, so it's not visible) or you could add a UITapGestureRecognizer to the view and use it to detect taps.

This is how you would achieve the desired border look on a view:

view.layer.borderColor = UIColor.lightGray.cgColor
view.layer.borderWidth = 1.0
view.layer.cornerRadius = 5.0

And this is how you would add a gesture recognizer:

let tapGesture = UITapGestureRecognizer(target: self, action: #selector(gestureRecognized(gesture:)))
view.addGestureRecognizer(tapGesture)

// Somewhere below in code put the method that is called when gesture is recognized
@objc
func gestureRecognized(gesture: UITapGestureRecognizer) {
    // Gesture recognized
}

Here is a short example how you should set things up:

import UIKit

protocol AnswerViewDelegate: AnyObject {
    func answerViewUserDidTap(sender: AnswerView)
}

class AnswerView: UIView {

    weak var delegate: AnswerViewDelegate?

    private lazy var tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(gesture:)))

    var answerId: String?
    var answer: String?

    override func awakeFromNib() {
        super.awakeFromNib()
        layer.borderColor = UIColor.lightGray.cgColor
        layer.borderWidth = 1.0
        layer.cornerRadius = 5.0

        addGestureRecognizer(tapGestureRecognizer)
    }

    @objc private func tapGestureRecognized(gesture: UITapGestureRecognizer) {
        delegate?.answerViewUserDidTap(sender: self)
    }

}

class ViewController: UIViewController {

    @IBOutlet private weak var answerView1: AnswerView!
    @IBOutlet private weak var answerView2: AnswerView!
    @IBOutlet private weak var answerView3: AnswerView!
    @IBOutlet private weak var answerView4: AnswerView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // This is just for demonstration purposes, to keep the code as short as possible
        [answerView1, answerView2, answerView3, answerView4].enumerated().forEach { offset, answerView in
            answerView?.answerId = String(offset)
            answerView?.answer = "Answer number: \(offset)"
            answerView?.delegate = self
        }
    }

}

// MARK: - AnswerViewDelegate

extension ViewController: AnswerViewDelegate {

    func answerViewUserDidTap(sender: AnswerView) {
        print("User tapped answer: \(String(describing: sender.answerId)) with id: \(String(describing: sender.answerId)).")
    }

}

Upvotes: 0

rbaldwin
rbaldwin

Reputation: 4860

You can achieve what you want with a custom UIButton class.

import UIKit

class CustomButton: UIButton {

    override func awakeFromNib() {
        super.awakeFromNib()

        layer.borderWidth = 1.0
        layer.borderColor = UIColor.systemGray4.cgColor
        layer.cornerRadius = 5.0
        contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 0.0)
    }
}

That will get you a button with a border and inset which looks like what you are trying to achieve:

enter image description here

A good way of checking whether the correct answer button is tapped would be to use the tag property on the UIButton. For example, 0 for the wrong answer, 1 for the correct answer. You can then check the tag value when each button is pressed. (all buttons connected to the same IBAction). If the correct button is pressed, you can set the border color to green, and do whatever else you need, before repeating the cycle.

import UIKit

class ViewController: UIViewController {

    @IBOutlet var answerAButton: CustomButton!
    @IBOutlet var answerBButton: CustomButton!
    @IBOutlet var answerCButton: CustomButton!
    @IBOutlet var answerDButton: CustomButton!

    var answerButtons: [UIButton]!

    override func viewDidLoad() {
        super.viewDidLoad()
        answerButtons = [answerAButton, answerBButton, answerCButton, answerDButton]
        setCorrectAnswer()
    }

    func setCorrectAnswer() {
        answerButtons.forEach {
            // Reset the border on each button
            $0.layer.borderColor = UIColor.systemGray4.cgColor
            // Reset the tag on each button
            $0.tag = 0
        }

        // Set the correct answer button tag
        answerButtons[2].tag = 1
    }

    @IBAction func answerTapped(_ sender: UIButton) {
        if sender.tag == 1 {
            sender.layer.borderColor = UIColor.green.cgColor
        }
    }
}

enter image description here


Answering your additional questions:

1. Where would I put the Custom Button?

2. What type of file should I create for that?

  1. Create a Swift file, named CustomButton.swift (or whatever you want to call it).
  2. In Storyboard use a standard UIButton, and in the Identity Inspector change the custom class to match. Make sure that the IBOutlets for your buttons are of type CustomButton for your IBOutlet connection. As shown above. You will not see the effects in Storyboard, only at runtime.

enter image description here

Upvotes: 1

Related Questions