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!
Related
I want to refer to a label based on the title of the button pressed.
I have a plenty of buttons, that I connected together in a single action. I want to be able to refer to only one label, that corresponds to the name of button pressed.
The Label names are: aLabel, bLabel, cLabel ...
The buttons pressed are titled "a", "b", "c"...
I ame creating the string with name of the label i want to refer to, but I can't use it as label name to change this particular label values.
I want to use this string to refer to the corresponding label, to change it title, color, and so on.
I tried, casting, looking for a function that changes strings into UILabels. I was also thinking about an array with pointers to the Labels, but I didn't succed even with establishing a pointer to a single Label...
//My code
#IBOutlet weak var a: UIButton!
#IBOutlet weak var b: UIButton!
#IBOutlet weak var c: UIButton!
#IBOutlet weak var aLabel: UILabel!
#IBOutlet weak var bLabel: UILabel!
#IBOutlet weak var cLabel: UILabel!
var currentLabel : String
#IBAction func FieldDisplay(_ sender: UIButton) {
currentLabel = sender.currentTitle! + "Label"
currentLabel.text = "OK"
}
//what I tried
(UILabel)currentLabel.text = "OK"
currentLabel = currentLabel.to.UIlabel
When interface objects are associated in multiple pairs like this — a series of button–label pairs, as you have it — there are two approaches commonly used. One is to assign each pair a tag. For example, in the storyboard, the first button would have tag 1, and the first label would have tag 101. Then the second button would have tag 2, and the second label would have tag 102. And so on.
So now in your IBAction function you look at the tag of the sender, add 100 to it, and call viewWithTag on your view to find the corresponding label.
The other possibility is to use outlet collections. Instead of three button outlets you have one array-of-button outlet; so too for the labels. Now in your IBAction function you get the firstIndex of the button in its array; that, if you've set this up correctly, is the index of the corresponding label in its array.
Remove all outlets and create 2 collections
#IBOutlet var allBts: [UIButton]!
#IBOutlet var allLbls: [UILabel]!
then hook all labels / btns to each , and set a tag for each group ( lbl1 && btn1 tag = 0 , lbl2 && btn2 tag = 1 and so on)
#IBAction func FieldDisplay(_ sender: UIButton) {
allLbls[sender.tag].text = sender.currentTitle! + "Label"
}
You can use value(forKey:) method
#IBAction func buttonTapped(_ sender: UIButton) {
if let currentLabel = value(forKey: sender.currentTitle! + "Label") as? UILabel {
currentLabel.text = "OK"
}
}
https://developer.apple.com/documentation/objectivec/nsobject/1412591-value
I am relatively new to Swift.
I have many images (though right now I am testing with four) which I am trying to hide (temporarily to make sure the foundational code is working, instead I really want to insert an image under the tapped image) when they are tapped.
I have created an array of ImageViews which I plan to expand once I have working code. I tried to add UITapGestureRecognizers to each ImageView using a for loop in addGestures() and then have selectImage() hide the tapped ImageView. The code compiles without error, but fails with uncaught NSException when one of these images is tapped. Any tips on how to do this effectively without too much manual coding for each image?
Code attached
Please check below code
func addGestureRecognizer(){
for imageView in imageArray{
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(gesture:)))
imageView.addGestureRecognizer(tap)
}
}
#objc func handleTap(gesture:UITapGestureRecognizer){
let imageView = gesture.view
imageView?.isHidden = true
}
in your code you're passing imageView instead of gesture on selector.however you can get imageView by gesture.view.
Also there is no need for separate function for enable userInteraction.
Here is what you can try
class GestureStackVC: UIViewController {
/// Image Outlets
#IBOutlet weak var img1: UIImageView!
#IBOutlet weak var img2: UIImageView!
#IBOutlet weak var img3: UIImageView!
/// ImageView Array
var imagesArray : [UIImageView]?
/// Image Gesture
var imageTapGesture : UITapGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
/// Allocate Array
imagesArray = [UIImageView]()
/// Add Required Values
imagesArray = [img1,img2,img3]
/// Add gesture
for imageView in imagesArray!{
let tap = UITapGestureRecognizer(target: self, action: #selector(imageTapHandler(_:)))
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(tap)
}
}
/// tap Handler
#objc func imageTapHandler(_ sender: UITapGestureRecognizer) {
/// Hide the Sender View
sender.view?.isHidden = true
/// Done
}
}
First Output with 3 Images
Output when clicked On first ImageView1 - Results its hidden
Output when clicked On first ImageView2 - Results its hidden
I have an app where a user can select a number of different buttons onscreen. When a user selects a button, it turns green and the text will be used in a later view. I am trying to make everything nice and swift by minimising the amount of code I am writing.
Every button is connected to the same action and their identity is determined by their tag. What I have done is created 2 arrays to track the card name and their on/off state. When a card is pressed the cardPressed function is called, this decides whether to turn the card green or white currently (it will do more later).
What I want to do is to perform the colour change in one line of code, instead of
cardOne.backgroundColor = UIColor.white
I want to do this [#1]
cardList[cardNumber].backgroundColor = UIColor.green
so that my outlet changes depending on the selection made. I would normally just have a massive switch statement that would read like so
switch cardList[cardNumber] {
case 0:
cardOne.backgroundColor = UIColor.green
case 1:
cardTwo.backgroundColor = UIColor.green
case 2:
cardThree.backgroundColor = UIColor.green
case So on so forth:
cardInfinity.......
default:
break
}
Obviously when I try to do [#1] I get an error because it is a string, not an outlet connection. What I would like to know, is there anyway to trick xcode into recognising it as an outlet, or better yet have a way to change the outlets I am acting upon in one line of code?
Hopefully I haven't rambled too much and you can understand my thought process! I have included all of the relevant code below, obviously it won't compile. If you have any ideas they would be appreciated, or if I'm being too optimistic and this isnt possible, just let me know :) for now I will be using a big switch statement! (maybe this is useful to me in the future!)
Thanks!
private let cardList = ["cardOne","cardTwo","cardThree"]
private var cardState = [false, false, false]
//Card functions
private func selectCard(cardNumber: Int){
cardState[cardNumber] = true
cardList[cardNumber].backgroundColor = UIColor.green
}
private func deselectCard(cardNumber: Int){
cardState[cardNumber] = false
//cardOne.backgroundColor = UIColor.white
}
//Decide which function to perform, based on the card information recieved
private func cardPressed(cardNumber: Int){
let selectedCardName = cardList[cardNumber]
let selectedCardState = cardState[cardNumber]
print("\(selectedCardName)")
print("\(selectedCardState)")
switch selectedCardState {
case true:
deselectCard(cardNumber: cardNumber)
case false:
selectCard(cardNumber: cardNumber)
}
}
//UI Connections
//Card button actions
#IBAction func buttonPressed(_ sender: UIButton) {
//Determine which button has been pressed
//let cardName = sender.currentTitle!
let cardSelection = sender.tag - 1
cardPressed(cardNumber: cardSelection)
}
//Card button outlets
#IBOutlet weak var cardOne: UIButton!
#IBOutlet weak var cardTwo: UIButton!
The solution lies in the wonderful world of object-oriented programming. Instead of using parallel arrays, you can create your own data type to group this data and behavior together.
If you created your own UIButton subclass, you could keep track of whether the button is selected with your own custom property, and make visual modifications as needed.
class CardButton: UIButton {
var isChosen: Bool = false {
didSet { backgroundColor = isChosen ? UIColor.green : UIColor.white }
}
}
If you set the buttons in the storyboard to be your new CardButton type, you can use their isChosen property in code.
Your buttonPressed function could look like this instead:
#IBAction func buttonPressed(_ sender: CardButton) {
sender.isChosen = !sender.isChosen
}
This would allow you to remove the majority of your existing code, since the data is stored inside each of your buttons.
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.
I have some images that need to be displayed dependent on the value of a slider. It is not working - where am I going wrong?
#IBOutlet weak var image1: UISlider! [this is connected to the slider NOT the image]
image1.setImage(UIImage(named:"image1.png"),forState:UIControlState.Selected)
#IBAction func sliderValue(sender: UIButton) {
let slider value = Int(sender.value)
if sliderValue == 1 {
image1.setImage
}
}
Maybe you want to do something like that ? You check the value of your slider each time its value change, then you set an image to your imageView.
#IBOutlet weak var imageView: UIImageView!
#IBAction func sliderValueChanged(sender: UISlider) {
var sliderValue = Int(sender.value)
if sliderValue == 1 {
self.imageView.image = UIImage(named: "foo")
}
}
To connect your Slider in your storyboard to your code :
In the right panel of your storyboard, select your ViewController, go to the third tab. In Custom Class > Class field, enter class you are using. It should autocomplete.
Click on "SHOW THE ASSISTANT EDITOR" (two circles icon) to split your window between your storyboard and your code.
Press Ctrl + drag the element (here your slider) to the code.
Choose Outlet or Action. For your imageView, choose outlet, because you will be able to interact with it programmatically, but the user won't do any action with it. I'll let you search Google if you need more details on IBOutlet and IBAction
Credits : Images from this site