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.
Related
I am learning Swift, and I am throwing myself in the deep end to force myself to learn the language. I have a nephew who is a baby and thought to make an app to help him learn numbers.
The app is designed to set a set number of buttons on the screen like the one provided below. I have the code to play Directions, which tells the user which number to select. A-N14a, the audio file, says to click the 4. The Done button is set to move to the next screen.
What I am asking is that if I want 4 to be pressed, and they press the 9, I want to know how to implement a feature to give a hint to click the number 4? The idea is to change the background to a button, but I don't know how to implement the feature. I am also open to other ideas. As a note, I do not know what to do, and I'm trying to learn, so the code provided is probably very simplistic and is at the beginning stages.
Below is an image of the screen and the code for that page.
ScreenShot of Page
import UIKit
import AVFoundation
class Intervention_Numerals1: UIViewController {
#IBOutlet weak var Directions: UIButton!
#IBOutlet weak var Done: UIButton!
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
setUpElements()
//Audio Test
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "A-N14a", ofType:"mp3")!))
audioPlayer.prepareToPlay()
} catch {
print(error)
}
}
func setUpElements() {
// Style the elements
Utilities.styleFilledButton(Directions)
Utilities.styleFilledButton(Done)
}
#IBAction func Play(_ sender: Any) {
audioPlayer.play()
}
}
Please let me know any tips or advice or links to similar questions, even though I could not find any on my own.
Here's what I would do:
Record the sound "Tap the number" and then the sounds for the numbers 0 through 9. Name the number sounds "0.mp3" through "9.mp3"
Create a storyboard with 4 buttons on it (like the picture you posted.)
Set up button IBOutlets buttonA - buttonD. Put those buttons into an array:
let buttonsArray = [buttonA, buttonB, buttonC, buttonD]
Fill an array with the numbers 0-9. Shuffle it. Remove 4 values put them into an array "buttonValues" (use the method removeLast().) The code to generate non-repeating values from 0-9 might look like this:
var randomNumbers = [Int]() //Define an array to hold random Ints
var lastValueReturned: Int?
//Function to return a random Int. It won't return the same value twice
func randomNumber() -> Int {
//Remove and return an item from the array
var result: Int
repeat {
//If the array is empty, fill it with the shuffled numbers 0...9
if randomNumbers.isEmpty {
randomNumbers += Array(0...9).shuffled()
}
result = randomNumbers.removeLast()
} while result == lastValueReturned
lastValueReturned = result
return result
}
Loop through your array of buttonValues and install the string for each number as the title of one of your buttons:
for index = 0...3 {
buttonsArray[index].setTitle("(buttonValues[index])", forSate: .normal)
}
Pick an index 0-3 to be the "correct" number.
let indexToPick = Int.random(in: 0...3)
Look up that value in buttonValues, and use it to pick a sound file to play:
let numberToPick = buttonValues[indexToPick]
let soundName = "\(numberToPick).mp3"
Load and play the "tap the number" sound, and then Load and play the sound for the selected number (soundName).
When the user taps a button, have the IBAction method use the sender parameter that is passed to it, and look in the array of buttons, buttonsArray, to see which button index was tapped.
If it is the correct button, take the success action.
If the tapped button index is not indexToPick, do an animation that changes the background color of the button at indexToPick, or the button's border width, or something, and then animates it back to normal. (Look at the UIView animate(duration:) family of methods for how to animate the button's background color. Use the form that takes an options: parameter, and set the .autoreverse option.)
If you're a newbie to iOS development, figuring out how to animate your correct answer button could be a challenge. I created a sample project that just animates one of 4 random buttons: https://github.com/DuncanMC/ButtonAnimation.git
The code for that project is as follows:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var buttonA: UIButton!
#IBOutlet weak var buttonB: UIButton!
#IBOutlet weak var buttonC: UIButton!
#IBOutlet weak var buttonD: UIButton!
//Define an empty array to hold buttons.
var buttonsArray = [UIButton]()
override func viewDidLoad() {
super.viewDidLoad()
//Put our button outlets into an array so we can reference them by index.
buttonsArray = [buttonA, buttonB, buttonC, buttonD]
//Give our buttons a cornerRadius so they look rounded when we add a border and fill color
for button in buttonsArray {
button.layer.cornerRadius = 10
}
}
#IBAction func handleAnimateButton(_ sender: UIButton) {
sender.isEnabled = false
//Pick a random button
let button = buttonsArray.randomElement()!
//Create an auto-reversing animation that fills the button with cyan, and draws a border around it.
//(Showing the border won't fade in and out, but it doesn't really matter)
UIView.animate(withDuration: 0.25,
delay: 0,
options: [.autoreverse, .curveEaseInOut],
animations: {
button.backgroundColor = .cyan
button.layer.borderWidth = 1.0
}, completion: {
success in
button.backgroundColor = .clear
sender.isEnabled = true
button.layer.borderWidth = 0
})
}
}
I would do as follows:
1. Create as many IBOutlets as your numbers (I suppose 0-9 for your example?) and link them to your buttons - E.g.
#IBOutlet weak var Button1: UIButton!
#IBOutlet weak var Button2: UIButton!
// Create as many as you need - Probably 10?
2. Create an IBAction and link it to all your buttons, with this code
#IBAction func checkCorrectAnswer(_ sender: UIButton) {
let arrayOfButtons:[UIButton] = [Button1, Button2] // Here you add all your buttons
let buttonTitle = sender.title(for: .normal)!
if buttonTitle == "YOUR CORRECT ANSWER" { //You have to substitute "YOUR CORRECT ANSWER" with the right string value
sender.backgroundColor = .green
} else {
sender.backgroundColor = .red
for i in arrayOfButtons {
if i.titleLabel?.text == "YOUR CORRECT ANSWER" { i.backgroundColor = .orange }
}
}
}
Enjoy!
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).
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 a UIButton inside my cell together with an image and a text label. I manage to change the image and label programatically, but the UIButton does not seem to respond to anything except isHidden.
This is my code, the button that is not changing is followButton:
import UIKit
class ProfileTableCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var profileImage: UIImageView!
#IBOutlet weak var followButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
self.profileImage.layer.borderWidth = 0.0;
self.profileImage.layer.cornerRadius = self.profileImage.frame.size.width/2;
self.profileImage.clipsToBounds = true
self.profileImage.image = UIImage(named: "belt")
self.name.text = "Bar Refaeli"
self.followButton.layer.borderColor = UIColor.black.cgColor
self.followButton.layer.borderWidth = 3.0;
self.followButton.layer.cornerRadius = self.frame.size.width/4
self.followButton.backgroundColor = UIColor.black
}
func setCell(image: UIImage, name: String){
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
The profileImage and name outlets change the appearance fine, like mentioned above.
I also tried to remove the button and bring it back in, clean xcode project, remove the outlet reference and connecting it again. Pretty frustrated by now.
I also tried to change the background color of the button through the storyboard, just for testing, and it does not change it! what does change is the titleLabel and the text color.
awakeFromNib()- Prepares the receiver for service after it has been loaded from an Interface Builder archive, or nib file.
Given that, move your code to a view initiating method like viewDidLoad or viewDidAppear(_:)
Child objects that are attributes like textLabels act differently than child view objects.
Eventually I actually solved this by tossing the table view to the garbage and implementing the same needs using a collection view. there was no problem there..