Creating buttons with a for loop? Swift - ios

func makeAButton(){
var tag = 1
for post in postArray {
let cgX = CGFloat(post.x!)
let cgY = CGFloat(post.y!)
let button : DragImg = DragImg(frame: CGRect(x: cgX, y: cgY, width: 100, height: 100))
button.backgroundColor = UIColor.blueColor()
button.setTitle(post.user, forState: .Normal)
button.addTarget(self, action: #selector(self.buttonAction), forControlEvents: .TouchUpInside)
button.tag = tag
self.view.addSubview(button)
print(tag)
tag += 1
}
}
I've currently got 7 things in my post array, and when I print out the tag, I get the
1
2
3
... and so on up to 7.
Whats happening though is that at the 0th item in the aray, 1 button is being created. Then at the 1st, 2 are being created at that x/y, and it keeps going up and up until I'm getting 7 buttons right ontop of eachother.
Why is this the case? I thought here the for loop would say:
For each post in PostArray make a single button using the data from that item in the PostArray.
How might I change this to work as intended?
Edit: (Just posting everything I've got)
import UIKit
import Firebase
class testController: UIViewController {
#IBOutlet weak var button : UIButton!
#IBOutlet weak var textField: UITextField!
var colorsArray = [String]()
var currentX = CGFloat()
var currentY = CGFloat()
var postArray = [CirclePost]()
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let position :CGPoint = touch.locationInView(view)
currentY = position.y
currentX = position.x
}
}
override func viewDidLoad() {
super.viewDidLoad()
DataService.ds.REF_BASE.child("Colors").observeEventType(.Value) { (snapshot: FIRDataSnapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
self.postArray = []
for snap in snapshots {
if let x = snap.value!["x"] as? CGFloat, y = snap.value!["y"] as? CGFloat, color = snap.value!["color"] as? String {
let post = CirclePost(postKey: snap.key, user: color , x: x, y: y)
self.postArray.append(post)
print(self.postArray.count)
self.makeAButton()
}
}
}
}
}
func makeAButton(){
var tag = 1
for post in postArray {
let cgX = CGFloat(post.x!)
let cgY = CGFloat(post.y!)
// let button = DragImg()
let button : DragImg = DragImg(frame: CGRect(x: cgX, y: cgY, width: 100, height: 100))
button.backgroundColor = UIColor.blueColor()
button.setTitle(post.user, forState: .Normal)
button.addTarget(self, action: #selector(self.buttonAction), forControlEvents: .TouchUpInside)
button.tag = tag
self.view.addSubview(button)
tag += 1
}
}
func buttonAction(sender: UIButton!) {
let button = postArray[sender.tag - 1]
print(button.user)
print("Button tapped")
}
#IBAction func buttonPressed(sender: AnyObject) {
let ref = DataService.ds.REF_BASE.child("Colors").childByAutoId()
ref.child("color").setValue(textField.text)
ref.child("x").setValue(currentX)
ref.child("y").setValue(currentY)
}
}

Here's your issue:
for snap in snapshots {
...
self.postArray.append(post)
print(self.postArray.count)
self.makeAButton()
You're adding a post to the array, then drawing the array (1 item). Then next time you add an additional post and then draw the array (2 items) and so on.
You need to move the call self.makeAButton() out of that for loop.

Related

Reposition other UIButton after delete one

I have a code where the user can input several ingredients and can also delete them.
Method: input ingredient in searchBar then create UIButton for that input and add into UIView. I also have a function to arrange position of all buttons properly
class SearchViewController: UIViewController {
var listInputView = UIView()
let spacingX: CGFloat = 10
let spacingY: CGFloat = 10
let btnHeight: CGFloat = 30
var heightConstraint: NSLayoutConstraint = NSLayoutConstraint()
var listBtn = [UIButton]()
//and also initialization of other components like UISearchBar,...
override func viewDidLoad() {
super.viewDidLoad()
setupSegmentControl()
setupView()
listInputView.translatesAutoresizingMaskIntoConstraints = false
heightConstraint = listInputView.heightAnchor.constraint(equalToConstant: 10)
}
func setupSegmentControl() {
//setup UISegmentedControl
}
fileprivate func setupView() {
//setup UISearchBar
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if segmentControl.selectedSegmentIndex == 0 {
//search from input menu
}
else {
//search from input ingredients
inputSearchBar.append(mainSearchBar.text!)
listIngredients()
}
}
func listIngredients() {
view.addSubview(listInputView)
listInputView.isHidden = false
NSLayoutConstraint.activate([
listInputView.topAnchor.constraint(equalTo: mainSearchBar.bottomAnchor, constant: 10),
listInputView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
listInputView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
heightConstraint
])
createIngredientBtn(ingredient: mainSearchBar.text!)
}
// create a button
func createIngredientBtn(ingredient: String) {
let deleteIcon = UIImage(systemName: "multiply")
let btn = UIButton(type: UIButton.ButtonType.custom) as UIButton
btn.setTitle(ingredient, for: UIControl.State.normal)
btn.setTitleColor(UIColor(hexString: "#707070"),for: UIControl.State.normal)
btn.layer.borderColor = UIColor(hexString: "#707070").cgColor
btn.layer.borderWidth = 1.0
btn.frame.size.height = btnHeight
btn.layer.masksToBounds = true
btn.contentEdgeInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
btn.setImage(deleteIcon, for: .normal)
btn.imageView?.contentMode = .scaleAspectFit
btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 3)
btn.semanticContentAttribute = UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft ? .forceLeftToRight : .forceRightToLeft
btn.addTarget(self, action: #selector(deleteSelectedInput), for: UIControl.Event.touchDown)
btn.translatesAutoresizingMaskIntoConstraints = false
listBtn.append(btn)
listInputView.addSubview(btn)
}
// delete a button
#objc func deleteSelectedInput(_ sender: UIButton) {
sender.removeFromSuperview()
listBtn = listBtn.filter {$0 != sender}
showListBtn() //I try to call this here to reposition other buttons
viewDidLayoutSubviews()
inputSearchBar = inputSearchBar.filter {$0 != sender.currentTitle}
if inputSearchBar.isEmpty {
listInputView.isHidden = true
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
showListBtn()
}
//Arrange position of each button and update height of inputView according to them
func showListBtn() {
let listInputViewWidth = listInputView.frame.size.width
var currentOriginX: CGFloat = 0
var currentOriginY: CGFloat = 0
listBtn.forEach { btn in
// break to newline
if currentOriginX + btn.frame.width > listInputViewWidth {
currentOriginX = 0
currentOriginY += btnHeight + spacingY
}
// set the btn frame origin
btn.frame.origin.x = currentOriginX
btn.frame.origin.y = currentOriginY
// increment current X
currentOriginX += btn.frame.width + spacingX
}
// update listInputView height
heightConstraint.constant = currentOriginY + btnHeight
}
}
Problem: Suppose I have > 1 input ingredients (> 1 button) and I delete one of it, the rest will be overlapped on each other at the leftmost origin (where 1st button is placed). At this moment, the position of them is actually correct, they just don't re-display as they should be.
However, when I try to input new ingredient, they will then display properly.
Here is the image to clarify this better:
Input 3 ingredients: Ing1, Ing2, Ing3
Delete Ing1. Then Ing2 and Ing3 are overlapped at the position of Ing1.
Add new input Ing4. They are all position correctly again.
Why the buttons are not re-position correctly when I delete one of them and how can I solve this ? Please kindly help me. Thank you.
EDIT: I'm able to call viewDidLayoutSubviews after delete but the buttons still not re-draw at new position although the frame.origin has changed correctly.
#objc func deleteSelectedInput(_ sender: UIButton) {
sender.removeFromSuperview()
listBtn = listBtn.filter {$0 != sender}
listInputView.removeAllSubviews()
listBtn.forEach { btn in
listInputView.addSubview(btn)
}
inputSearchBar = inputSearchBar.filter {$0 != sender.currentTitle}
if inputSearchBar.isEmpty {
listInputView.isHidden = true
}
}

Fade image of button from one image to another via a float value

In my use case, I have a slider that moves from 0.0 to 1.0. When moving that slider I want a button's image to fade from one image to another smoothly. The button will only transition between two images. As one fades in, the other fades out and vice-versa.
There are answers on SO on how to change the image of a button over a fixed duration but I can't find something based on a changing float.
So is there a way to change the image of a button over a duration from one image to another based on a float that changes from 0.0 to 1.0?
EDIT:
For example, imagine a button that transitions smoothly from an image of a red star to a green star as you move the slider back and forth. (I know you could accomplish this other ways but I want to use two images)
Give this a shot. It is doing what you want I think. The trick is to set the layer speed to 0 and just update the timeOffset.
import UIKit
class ViewController: UIViewController {
lazy var slider : UISlider = {
let sld = UISlider(frame: CGRect(x: 30, y: self.view.frame.height - 60, width: self.view.frame.width - 60, height: 20))
sld.addTarget(self, action: #selector(sliderChanged), for: .valueChanged)
sld.value = 0
sld.maximumValue = 1
sld.minimumValue = 0
sld.tintColor = UIColor.blue
return sld
}()
lazy var button : UIButton = {
let bt = UIButton(frame: CGRect(x: 0, y: 60, width: 150, height: 150))
bt.center.x = self.view.center.x
bt.addTarget(self, action: #selector(buttonPress), for: .touchUpInside)
bt.setImage(UIImage(named: "cat1"), for: .normal)
return bt
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(button)
self.view.addSubview(slider)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
addAnimation()
}
func addAnimation(){
let anim = CATransition()
anim.type = kCATransitionFade
anim.duration = 1.0
button.layer.add(anim, forKey: nil)
button.layer.speed = 0
button.setImage(UIImage(named: "cat2"), for: .normal)
}
#objc func sliderChanged(){
print("value \(slider.value)")
button.layer.timeOffset = CFTimeInterval(slider.value)
}
#objc func buttonPress(){
print("pressed")
}
}
Here is the result:
For Swift 4
//This is slider
#IBOutlet weak var mSlider: UISlider!
//Add Target for valueChanged in ViewDidLoad
self.mSlider.addTarget(self, action: #selector(ViewController.onLuminosityChange), for: UIControlEvents.valueChanged)
//called when value change
#objc func onLuminosityChange(){
let value = self.mSlider.value
print(value)
if value > 0.5 {
btnImage.imageView?.alpha = CGFloat(value)
UIView.transition(with: self.btnImage.imageView!,
duration:0.3,
options: .showHideTransitionViews,
animations: { self.btnImage.setImage(newImage, for: .normal) },
completion: nil)
}else{
btnImage.imageView?.alpha = 1 - CGFloat(value)
UIView.transition(with: self.btnImage.imageView!,
duration:0.3,
options: .showHideTransitionViews,
animations: { self.btnImage.setImage(oldImage, for: .normal) },
completion: nil)
}
}
Or Inset an Action directly via Storyboard/XIB
This is another workaround, since i tired with single button but couldn't achieve the result, so what i did is i took two buttons overlaying each other, with same size, constraints and each with separate image.
var lastSlideValue : Float = 0.0
#IBOutlet weak var slideImage: UISlider!
#IBOutlet weak var btnImageOverlay: UIButton!
#IBOutlet weak var btnSecondOverlayImage: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
btnImageOverlay.imageView?.alpha = 1.0
btnSecondOverlayImage.imageView?.alpha = 0.0
}
#IBAction func sliderValueChanged(_ sender: Any) {//UIControlEventTouchUpInside connected method
let sliderr = sender as? UISlider
let value = sliderr?.value
if lastSlideValue < (sliderr?.value ?? 0.0) {
btnSecondOverlayImage.imageView?.alpha = CGFloat(value!)
btnImageOverlay.imageView?.alpha = 1 - CGFloat(value!)
} else if lastSlideValue == sliderr?.value {
print("equal")
} else {
btnImageOverlay.imageView?.alpha = CGFloat(value!)
btnSecondOverlayImage.imageView?.alpha = 1 - CGFloat(value!)
}
}
#IBAction func userTouchedSlider(_ sender: Any) { //UIControlEventValueChanged connected method
let sliderr = sender as? UISlider
lastSlideValue = (sliderr?.value)!
}
You could use cross fade animations with single button, but binding it with slider is difficult task. Let me know if you need any assistance with the same.

Firebase Data Returning Nil

I am trying to create programmatic Radio Buttons based on dynamic Firebase data. The number of Radio Buttonsis dependent on an integer value stored in Firebase, named numberOfChildren.
The value I am receiving from Firebase is coming back nil and I cannot figure out why. Any help on how to resolve this issue so that I can return an integer value would be appreciated:
import UIKit
import FirebaseDatabase
import DLRadioButton
class PollController: UIViewController {
#IBOutlet weak var passLabel: UILabel!
#IBOutlet weak var pollImage: UIImageView!
var ref: FIRDatabaseReference!
var pollRef: FIRDatabaseReference!
var pass = ""
var passedImageURL = ""
var posX = 0;
var posY = 0;
var numberOfChildren: Int!
let label2 = UILabel(frame: CGRect(x: 90, y: 160, width: 200, height: 70))
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference()
pollRef = ref.child("Polls").child(pass)
passLabel.text = pass
pollImage.sd_setImage(with: URL(string: passedImageURL), placeholderImage: UIImage(named: "test"))
pollRef.observe(FIRDataEventType.value, with: {(snapshot) in
self.numberOfChildren = Int(snapshot.childSnapshot(forPath: "answers").childrenCount)
self.passLabel.text = String(self.numberOfChildren)
print(self.numberOfChildren)
})
var buttons = [DLRadioButton]()
for x in 0..<self.numberOfChildren {
let firstRadioButton = self.createRadioButton(frame: CGRect(x: CGFloat(x)*32, y: self.view.center.y, width: 40.0, height: 20.0), title: String(x), color: UIColor.green)
firstRadioButton.tag = x
buttons.append(firstRadioButton)
self.view.addSubview(firstRadioButton);
}
let groupButtons = DLRadioButton()
groupButtons.otherButtons = buttons
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func createRadioButton(frame : CGRect, title : String, color : UIColor) -> DLRadioButton {
let radioButton = DLRadioButton(frame: frame);
radioButton.titleLabel!.font = UIFont.systemFont(ofSize: 14);
radioButton.setTitle(title, for: UIControlState.normal);
radioButton.setTitleColor(color, for: UIControlState.normal);
radioButton.iconColor = color;
radioButton.indicatorColor = color;
radioButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center;
radioButton.addTarget(self, action: #selector(self.logSelectedButton(_:)), for: UIControlEvents.touchUpInside);
return radioButton;
}
#objc private func logSelectedButton(_ sender: DLRadioButton){
print("Selected Button Tag = \(sender.tag) and Title \(sender.titleLabel?.text)")
}
}
The problem is in the way you nest the code. Firebase loads the data asynchronously, that's why you pass in a callback block: so that it can call your code block once the data has loaded. By the time you look over self.numChildren that data hasn't loaded yet.
The solution is to move the code that requires numChildren into the callback block.
pollRef.observe(FIRDataEventType.value, with: {(snapshot) in
self.numberOfChildren = Int(snapshot.childSnapshot(forPath: "answers").childrenCount)
self.passLabel.text = String(self.numberOfChildren)
var buttons = [DLRadioButton]()
for x in 0..<self.numberOfChildren {
let firstRadioButton = self.createRadioButton(frame: CGRect(x: CGFloat(x)*32, y: self.view.center.y, width: 40.0, height: 20.0), title: String(x), color: UIColor.green)
firstRadioButton.tag = x
buttons.append(firstRadioButton)
self.view.addSubview(firstRadioButton);
}
let groupButtons = DLRadioButton()
groupButtons.otherButtons = buttons
})
This way the loop is only invoked once numChildren has been initialized from the database.

How to remove a button when it is clicked?

I'm very new to swift. I've created multiple textfield and button by using subview. The output of my ViewController is below:-
Now I need to remove "-" button and it's corresponding textfield when it is clicked.
But I'm not able to detect which button is being clicked.
This is my code:
var y: CGFloat = 190
var by: CGFloat = 192
#IBAction func addRow(sender: AnyObject) {
y += 30
by += 30
let textFiled = UITextField(frame:CGRectMake(50.0, y, 100.0, 20.0))
textFiled.borderStyle = UITextBorderStyle.Line
let dunamicButton = UIButton(frame:CGRectMake(155.0, by, 15.0, 15.0))
dunamicButton.backgroundColor = UIColor.clearColor()
dunamicButton.layer.cornerRadius = 5
dunamicButton.layer.borderWidth = 1
dunamicButton.layer.borderColor = UIColor.blackColor().CGColor
dunamicButton.backgroundColor = .grayColor()
dunamicButton.setTitle("-", forState: .Normal)
dunamicButton.addTarget(self, action: #selector(removeRow), forControlEvents: .TouchUpInside)
self.view.addSubview(textFiled)
self.view.addSubview(dunamicButton)
}
func removeRow(sender: UIButton!) {
print("Button tapped")
self.view.removeFromSuperview()
}
any help would be appreciated...
eMKA was right! Try this:
import UIKit
class ViewController: UIViewController {
var y:CGFloat = 100
var textFields = [UIButton : UITextField]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func onAddMoreButtonPressed(sender: AnyObject) {
let newButton = UIButton(frame: CGRect(x: 50, y: y, width: 150, height: 20))
newButton.setTitle("New button", forState: .Normal)
newButton.backgroundColor = UIColor.blueColor()
newButton.addTarget(self, action: #selector(ViewController.onNewButtonPressed(_:)), forControlEvents: .TouchUpInside)
self.view.addSubview(newButton)
let newTextField = UITextField(frame: CGRect(x: 200, y: y, width: 150, height: 20))
newTextField.text = "New text field"
self.view.addSubview(newTextField)
textFields[newButton] = newTextField
y += 20
if y > self.view.frame.height {
y = 100
}
}
func onNewButtonPressed(sender: UIButton) {
textFields[sender]?.removeFromSuperview()
sender.removeFromSuperview()
}
}
You can override the method touchesBegan in any view controller. Assuming that you are storing an array of your buttons somewhere
let buttons : [UIButton] = []
you can do the following:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
guard let touch: UITouch = touches.first else {
return
}
for button in buttons {
if touch.view == button {
print("This is the button you tapped")
button.removeFromSuperview()
}
}
}

How can I make empty circle on baseball out count?

I am making baseball outcount code based on FoodTracker source code. Here's my code but empty circle doesn't count in this code. How can I make the empty circle rating? I would appreciate if you guys can help me out. Thank you!!
///////
import UIKit
class RatingControl: UIView {
// MARK: Properties
var rating = 0 {
didSet {
setNeedsLayout()
}
}
var ratingButtons = [UIButton]()
var spacing = 3
var circles = 3
// MARK: Initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let filledCircleImage = UIImage(named: "checkedRed")
let emptyCircleImage = UIImage(named: "UncheckedRed")
for _ in 0..<3 {
let button = UIButton()
button.setImage(emptyCircleImage, forState: .Normal)
button.setImage(filledCircleImage, forState: .Selected)
button.setImage(filledCircleImage, forState: [.Highlighted, .Selected])
button.adjustsImageWhenHighlighted = false
button.addTarget(self, action: "ratingButtonTapped:", forControlEvents: .TouchDown)
ratingButtons += [button]
addSubview(button)
}
}
override func layoutSubviews() {
// Set the button's width and height to a square the size of the frame's height.
let buttonSize = Int(frame.size.height)
var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize)
// Offset each button's origin by the length of the button plus spacing.
for (index, button) in ratingButtons.enumerate() {
buttonFrame.origin.x = CGFloat(index * (buttonSize + spacing))
button.frame = buttonFrame
}
updateButtonSelectionStates()
}
override func intrinsicContentSize() -> CGSize {
let buttonSize = Int(frame.size.height)
let width = (buttonSize + spacing) * circles
return CGSize(width: width, height: buttonSize)
}
// MARK: Button Action
func ratingButtonTapped(button: UIButton) {
rating = ratingButtons.indexOf(button)! + 1
updateButtonSelectionStates()
}
func updateButtonSelectionStates() {
for (index, button) in ratingButtons.enumerate() {
// If the index of a button is less than the rating, that button should be selected.
button.selected = index < rating
}
}
}
if you want zero to count then return 0 as a possible button action. Your rating is already set to zero so technically it does count. If you accidentally tap the first circle and want to undo it you can try something like this in ratingButtonTapped
//rating = ratingButtons.index(of: button)! + 1
rating = (rating == 1) && (ratingButtons.index(of: button) == 0) ? 0 : (ratingButtons.index(of: button)! + 1)

Resources