I'm working on a crossword puzzle app for swift and I am having trouble getting a UIButton from a subview in the UIButton that is a textfield.
The text field takes up the space where the UIButton title should be, but when clicking on the textfield it doesn't click the UIButton.
The UIButton itself as of now, highlights all the UIButtons of the some column and row.
There a few things I've tried such as graving the superclass of the subview
var myButton: CustomButton = textfield.superclass as? CustomButton
and I also tried using
var myObject : CustomButton? {
return view.compactMap({$0 as? CustomButton }).first
}
In the CustomButton.swift
class CustomButton: UIButton {
var number: Int = 0
var letter: Character?
var textField: UITextField!
var cornerLabel: UILabel!
// this init will intialize the button through programatically
override init(frame: CGRect) {
super.init(frame: frame)
textField = UITextField(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
textField.translatesAutoresizingMaskIntoConstraints = false
textField.center = .zero
textField.textAlignment = .center
textField.text = ""
I have a ButtonStore.swift that stores all the CustomButtons in the array so I can manage them and retrieve certain ones.
And the MainController.swift has all the reference CustomButton. I am using a UITextFieldDelegate from the MainController
class MainController : UIViewController, UITextFieldDelegate{
var buttonStore : ButtonStore!
#IBOutlet var A1 : CustomButton!
#IBAction func buttonIsPress(sender: CustomButton){
let button : CustomButton = sender
let identifier : String = sender.accessibilityIdentifier ?? ""
// Clear all backgroundbox back to white unless the background is black
buttonStore.removeHighlights()
// Highlight the button pressed and its column and row
buttonStore.highlightRowColumn(identifier: identifier)
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool
{
print("something happened")
// TRIED RECEIVING THE CUSTOMBUTTON HERE
return true
}
The expected result is to have had the CustomButton trigger when textfield is pressed or grabbing the CustomButton of the textfield pressed so I use the CustomButton as reference.
You can get the superview by
if let button = textField.superview as? CustomButton {
// Do what you want to here
}
You can grab any superview or any parent view controller generically in a type safe way by walking up the responder chain (because both UIViewController and UIView inherit from UIResponder and implement its next method):
extension UIResponder {
func firstParent<T: UIResponder>(ofType type: T.Type ) -> T? {
return next as? T ?? next.flatMap { $0.firstParent(ofType: type) }
}
}
Use like:
if let button = textField.firstParent(ofType: CustomButton.self) {
//Do button stuff here
}
This method has the advantage that you can find the next parent of a particular type even if it isn't the immediate parent (ie you may have several views between the textfield and the CustomButton and this still works, while calling superview does not).
Related
I created two views in Main.storyboard with
class ViewController: UIViewController
{
var cardsOnTableCounts = 0
#IBOutlet weak var headStackView: HeadStackView!
#IBOutlet weak var gridCardView: GridView!
#objc func touchDeal3Card ()
{
gridCardView.cellCounts += 3
}
}
I created a button in class HeadStackView
class HeadStackView: UIView
{
var deal3Card: UIButton = UIButton.init()
override func draw(_ rect: CGRect) {
setDeal3Card()
}
private func setDeal3Card () {
let leftButtonFrame : CGRect = CGRect(x: bounds.maxX/15, y: bounds.maxY/6, width: bounds.maxX/3, height: bounds.maxY*0.7)
deal3Card = UIButton.init(frame: leftButtonFrame)
deal3Card.backgroundColor = UIColor.purple
deal3Card.setTitle("Deal 3 Cards", for: .normal)
deal3Card.addTarget(self, action: #selector(ViewController.touchDeal3Card), for: .touchUpInside)
addSubview(deal3Card)
}
In GridView.swift
class GridView: UIView
{
var cellCount: Int = 5 {didSet { setNeedsDisplay(); setNeedsLayout()}}
override func layoutSubviews() {
super.layoutSubviews()
drawGrid() // it draws cellCount of cells
}
}
My goal is when the UIButton deal3Card is pressed, gridCardView will redraw itself with more cards on gridCardView.
The code above passed compiler and show the button (deal3card). but when I click on button, it gets exception:
2018-10-23 19:52:54.283032+0200 gridTest[23636:5175258]
-[gridTest.HeadStackView touchDeal3Card]: unrecognized selector sent to instance 0x7fb97180c8f0
2018-10-23 19:52:54.287469+0200 gridTest[23636:5175258] ***
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[gridTest.HeadStackView
touchDeal3Card]: unrecognized selector sent to instance
0x7fb97180c8f0'
The proximate cause of the crash is this line:
deal3Card.addTarget(self,
action: #selector(ViewController.touchDeal3Card), for: .touchUpInside)
The target self is wrong. You want to send this message to the view controller, which implements touchDeal3Card. But in your code, self is the button, which doesn't.
The simplest solution, given the architecture you've constructed, is to replace self with nil. This will cause the touchDeal3Card to percolate up the responder chain and reach the view controller.
Having said that, I would suggest that the architecture itself is wrong. View controller should control views; views should not control themselves. The view controller, not the view, should be creating the button. This is well indicated by the fact that this code is totally wrong:
override func draw(_ rect: CGRect) {
setDeal3Card()
}
That is a total misuse of draw and is going to land you in terrible trouble. The only thing you should do in draw is (wait for it) draw. Adding subviews in draw is as wrong as anything could possibly be.
I have a UITableView, where each row is a custom XIB. One row has 4 UIButtons, that are choices, like Question, Information, etc. When the UIButton is tapped I display an animation in a CALayer. When the table view scrolls, the CALayer is removed, that is the animation is gone.
When the button is tapped, I crate the CALayer and the animation begins. How can I make sure the CALayer doesn't disappear when the table is scrolled or updated?
class ReasonForSupportTableViewCell: UITableViewCell {
let animationView = AnimationView()
var hasButtonBeenTapped = false
var previousButtonTapped: UIButton? = nil
//IBOutlets
#IBOutlet weak var questionButton: UIButton!
#IBOutlet weak var informationButton: UIButton!
#IBOutlet weak var crashButton: UIButton!
#IBOutlet weak var bugButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
//set initial button
questionButton.sendActions(for: .touchUpInside)
}
func createView(sender: UIButton) {
//draw the frame
animationView.frame = CGRect(x: 0, y: 0, width: 75.0, height: 75.0)
//change the view background color
animationView.backgroundColor = UIColor.clear
animationView.isUserInteractionEnabled = false
//add the view.
sender.addSubview(animationView)
}
#IBAction func buttonTapped(_ sender: UIButton) {
if previousButtonTapped == nil {
//you haven't tapped a button yet
createView(sender: sender)
animationView.animateTo()
previousButtonTapped = sender
} else if previousButtonTapped == sender {
animationView.removeAnimation()
previousButtonTapped = nil
} else {
animationView.removeAnimation()
createView(sender: sender)
animationView.animateTo()
previousButtonTapped = sender
}
}
}
Subclass UIButton to add the CALayer on initialisation, rather than when a button is tapped. You can then use the prepareForReuse() function to stop any animation.
I have got a label that appears in the NavigationBar when the View of the FirstViewController opens for the first time. The label shows a number that should be able to change. If I click a button on the FirstViewController the View of the SecondViewController shows up in which's NavigationBar the label of the FirstViewController is still visible. When I change the number that is written in the label by clicking on a button in the SecondViewController the number of the label only changes when I go back to the View of the FirstViewController. That is because I update the title in the ViewDidLoad String loop.
Now my question is:
I want the number of the label to be changed at the moment when I click the button in the SecondViewController, although the label was defined in the Code of the FirstViewController. The number of the label shows an amount of money.
This is the Code of the FirstViewController:
var moneyLabel: UILabel?
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationLabel()
let newMoney:String = String(format: "%f", money)
updateTitle(title: "\(newMoney)")
}
func updateTitle(title: String) {
if let myTitleView = self.moneyLabel {
myTitleView.text = title
}
}
func setupNavigationLabel() {
let navigationBar = self.navigationController?.navigationBar
let moneyFrame = CGRect(x: 300, y: 0, width:
(navigationBar?.frame.width)!/2, height: (navigationBar?.frame.height)!)
moneyLabel = UILabel(frame: moneyFrame)
moneyLabel?.text = "\(money)"
navigationBar?.addSubview(moneyLabel!)
}
It seems that your problem is with the second ViewController but you are not sharing any code!!! Use the below to achieve your objective.
class secondViewControler:UIViewController{
var money:Int
var moneyLabel:UILAbel?
override func viewDidLoad(){
self.money= //pass the amount, i dont know which way you use to store and retrieve the amount
moneyLabel.text="\(money)"
}
#IBAction updateMoney(){
//get the amount from the textfield say the output is X
money=X
moneyLabel.text="\(money)"
}
}
My View hierarchy looks like this:
ElevethViewController of type UIViewController
Container View
ManagedTableEleventhViewController of type UITableViewController embedded in Container View
ManagedTableEleventhViewController contains 4 static cells containing 1 textField each and one empty static cell.
class ManagedTableEleventhViewController: UITableViewController,UITextFieldDelegate {
var hasText:Bool!
#IBOutlet weak var fullName: UITextField!
#IBOutlet weak var flatNumber: UITextField!
#IBOutlet weak var streetAddress: UITextField!
#IBOutlet weak var phoneNumber: UITextField!
//checkValue takes ELViewController parameter so that segue can be
//performed when button is touched in EleventhViewController
func checkValue(ELViewController:EleventhViewController) {
//loop through the textfields and check if they have text
for case let textField as UITextField in viewController.view.subviews {
//print is not executed meaning loop is not performed
print("some text")
if textField.text == "" {
self.hasText = false
textField.layer.borderColor = UIColor.red.cgColor
} else {
print("true value in for loop")
self.hasText = true
performSegue(withIdentifier: "elevethToTwelveth", sender: ELViewController)
}
}//end of for loop
}
class EleventhViewController: UIViewController {
var nextButtonOutlet:UIButton!
override func viewDidLoad() {
super.viewDidLoad()
//create button programmatically
var button = UIButton(type: UIButtonType.custom) as UIButton
button = UIButton(frame: CGRect(x: 0, y: 637, width: 375, height: 50))
button.titleLabel?.textColor = UIColor.white
button.backgroundColor = UIColor(colorLiteralRed: 117/255, green: 232/255, blue: 0, alpha: 1)
button.setTitle("Next", for: .normal)
button.addTarget(self, action: #selector(EleventhViewController.nextButton), for: .touchUpInside)
self.view.addSubview(button)
self.nextButtonOutlet = button
}
func nextButton(sender: UIButton) {
//create instance of tableView
let managedTable = ManagedTableEleventhViewController()
managedTable.checkValue(viewController: self)
} //end of EleventhViewController class
Well first I can give you an answer that might satisfy you and fix your loop but I would recommend not doing it that way to alter your textfields. I would recommend doing it in cellForRow even though they may be static cells. Depending on your view setup in the cells it would look like this if the textfield is added directly to the cells and not to another view.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Testing")
for cell in tableView.visibleCells{
for sub in cell.contentView.subviews{
if sub is UITextField{
print("Textfield")
}
}
}
}
Just to follow up, if this is for validation you should'nt be only checking "" case, because you allow " ", " " etc. Use isEmpty, it should work better, if you only want to check existence of text.
Also you dont have to extract fields from subviews as you already have properties, i'm not sure if you have any other reason for this logic though.
Edit. Ooops, i just noticed your checking for textfields in a controller which does not have any visible fields, so normally your check never passes.
I think you should'nt even validate textfields for one class in another class, unless its a class handling textfield validation in general.
In EleventhViewController you have no textfields, so nothing to validate.
class TestViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var mySearchBar: UISearchBar!
var myOwnInputView = UIView(frame: CGRectMake(100,100,50,50))
overide func viewDidLoad() {
mySearchBar.delegate = self
mySearchBar.inputView = myOwnInputView // errors - "Cannot assign to the result of this expression
}
}
I'm trying to assign a custom input view to my search bar, however based on apple documentation (-sorry could not find link anymore), for UISearchBar it seems to be a read-only value. Looking at this post, it appears that UISearchbar has multiple subviews and I need to get to the UITextField part in order to change the inputview. However, I'm not sufficiently familiar with Obj-C and my attempts to convert the code to Swift have not been successful.
This is the simplest way:
let searchTextField = searchBar.valueForKey("_searchField") as! UITextField
searchTextField.inputView = myOwnInputView
Found out that the textfield is a subview of a subview of UISearchbar.
Code to get to the textfield in swift:
var fakeView: UIView = UIView(frame: CGRectMake(100, 100, 50, 50))
#IBOutlet weak var searchBar: UISearchbar!
override func viewDidLoad() {
fakeView.backgroundColor = UIColor.redColor()
var c = 0
for v in (self.searchBar.subviews[0]).subviews {
c++
println("\(c).\(v)") //you should see two views - UISearchBarBackground and UISearcBarTextField
if let tf = v as? UITextField {
//do stuff to tf here.
//in my case, what I want is:
tf.inputView = fakeView
break
}
}
}}
Result of above code is no keyboard will show up when the searchbar text field is touched, just a red rectangle.
Note - credit goes to Matt Neuburg's "Programming iOS8: Dive Deep into Views, ViewControllers, and Frameworks (ISBN: 978-1491908730). Chapter 8 to be precise.