I have been working through the Food Tracker app tutorial as a project to learn Swift. I understand that this is full of bugs, but I have been researching ways to correctly format my code. I got to the star rating section, added the red button, separated it into 5 buttons, then added the stars. The stars showed up for a bit, but now do not show up in my simulator at all. I have cleaned my code, disconnected and added images and image code, disconnected and added my IBOutlets, etc. They still are not showing so I am thinking there has to be something I am missing in my code since I am new to this. Any help would be much appreciated.
import UIKit
class RatingControl: UIView {
// MARK: Properties
var rating = 0 {
didSet {
setNeedsLayout()
}
}
var ratingButtons = [UIButton]()
var spacing = 5
var stars = 5
// MARK: Initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let filledStar = UIImage(named: "filledStar")
let emptyStar = UIImage(named: "emptyStar")
for _ in 0..<5 {
let button = UIButton()
button.setImage(emptyStar, for: UIControlState())
button.setImage(filledStar, for: .selected)
button.setImage(filledStar, for: [.highlighted, .selected])
button.adjustsImageWhenHighlighted = false
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), for: .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.enumerated() {
buttonFrame.origin.x = CGFloat(index * (buttonSize + spacing))
button.frame = buttonFrame
}
updateButtonSelectionStates()
}
override var intrinsicContentSize : CGSize {
let buttonSize = Int(frame.size.height)
let width = (buttonSize + spacing) * stars
return CGSize(width: width, height: buttonSize)
}
// MARK: Button Action
func ratingButtonTapped(_ button: UIButton) {
rating = ratingButtons.index(of: button)! + 1
updateButtonSelectionStates()
}
func updateButtonSelectionStates() {
for (index, button) in ratingButtons.enumerated() {
// If the index of a button is less than the rating, that button should be selected.
button.isSelected = index < rating
}
}
}
In your init?() method, you've missed adding button to ratingButtons array as a result nothing is turned up when the array is enumerated and accessed, and it is empty
Please add the following
self.ratingsButton.append(button)
right after
addSubview(button)
Hopefully the buttons will start showing up.
Related
I am trying to add UIButtons in a UIView, buttons width will change depending on their title. So it could be one button in a row or 3 buttons in a row. If there is no space in same row button should be added in the next row. Something like this
and UIView height should increase if more lines are added up-to a certain height.
I am thinking to start with UIStackViews but not sure how to achieve this functionality. Any help is appreciated.
A UICollectionView using a UICollectionViewFlowLayout would do this by default. The only thing you would have to do is update the height of the collevtionView to match its content size if you don't want it to certainly scroll.
Using a UIStackView would require you to calculate the the size of every button and move down to a new row when they don't fit. While that doesn't sound to hard, it's a lot of layout math you'll have to do.
I was able to do this with a custom button generator
import Foundation
import UIKit
class ButtonGenerator {
private var buttonTitleArray = [String]()
private var buttonBackgroundColor: UIColor = UIColor.white
private var buttonFontColor: UIColor = UIColor.black
private var passedFunctionCall: Selector?
private var passedParentView = UIScrollView()
private var parentViewWidth = CGFloat()
private var desiredMargin = CGFloat()
private var buttonsToReturn = [UIButton]()
func setButtonStyle(bkgColor:UIColor,fontColor:UIColor) {
buttonBackgroundColor = bkgColor
buttonFontColor = fontColor
}
func setMargin(to margin:CGFloat) {
desiredMargin = margin
}
func addButtons(to parentView:UIScrollView,withTitles passedTitles [String],calling function:Selector){
//Receive Passed Arguments
passedParentView = parentView
parentViewWidth = parentView.frame.width
buttonTitleArray = passedTitles
passedFunctionCall = function
//Track current origin coordinates
var XPos = desiredMargin
var YPos = desiredMargin
var buttonHeight = CGFloat()
for i in 0..<buttonTitleArray.count {
let buttonToAdd = UIButton(type: .custom)
buttonToAdd.backgroundColor = buttonBackgroundColor
buttonToAdd.setTitleColor(buttonFontColor, for: .normal)
buttonToAdd.setTitle(buttonTitleArray[i], for: .normal)
buttonToAdd.sizeToFit()
buttonHeight = buttonToAdd.frame.height
buttonToAdd.addTarget(self, action: passedFunctionCall!, for: .touchUpInside)
if XPos + buttonToAdd.frame.width < parentViewWidth-(2*desiredMargin) {
//buttonToAdd won't be clipped --> Okay to add to current row
buttonToAdd.frame.origin.x = XPos
buttonToAdd.frame.origin.y = YPos
//no change in row --> only update X position
XPos += buttonToAdd.frame.width + desiredMargin
}
else {
//buttonToAdd will be clipped --> display on next row
XPos = desiredMargin
YPos += buttonHeight + desiredMargin
//set button position
buttonToAdd.frame.origin.x = XPos
buttonToAdd.frame.origin.y = YPos
//update X position
XPos += buttonToAdd.frame.width + desiredMargin
}
//Add Button to return array
buttonsToReturn.append(buttonToAdd)
}
//Set YPos to position of content bottom
YPos += buttonHeight + desiredMargin
//Set content size of the parent scroll view to the generated height
passedParentView.contentSize = CGSize(width: parentViewWidth, height: YPos)
//Add the buttons to the scroll view
for i in 0..<buttonsToReturn.count {
passedParentView.addSubview(buttonsToReturn[i])
}
}
}
Then to call the button generator in your ViewController you'd add the following:
let btnGen = ButtonGenerator()
btnGen.setButtonStyle(bkgColor: UIColor.white, fontColor: UIColor.blue)
btnGen.setMargin(to: 10)
btnGen.addButtons(to: hashtagScrollView, withTitles: PostModel.Post.genericMusicHashtags, calling:#selector(buttonClicked(sender:)))
Also, because of the selector, you'll have to add an #objc func to your ViewController Class.
#objc func buttonClicked(sender: UIButton) {
print(sender.titleLabel!.text!)
}
It doesn't have the limiting height functionality, but you should be able to modify the button generator to include an if statement to stop adding buttons.
This question already has an answer here:
how to make designable textfield code class in swift
(1 answer)
Closed 5 years ago.
I am a beginner, I am trying to make a password textfield with two image, one on the left side (lock image) and another one on the right side to reveal the password like this one
but my senior ordered me to make it in the #IBDesignable class of text field so the main code stay clear from UI. i find something similar thread about this in here how to make designable textfield code class in swift
but it seems that the code is for left image only or right image only, i need both of them (lock and eyes symbol) appear in the textfield. I have tried to modify the code there, but i can't achieve what i need. really need your help, maybe you have done this in your previous project :)
import UIKit
#IBDesignable
class DesignableTextField: UITextField {
#IBInspectable var rightImage : UIImage? {
didSet {
updateRightView()
}
}
#IBInspectable var rightPadding : CGFloat = 0 {
didSet {
updateRightView()
}
}
#IBInspectable var leftImage : UIImage? {
didSet {
updateView()
}
}
#IBInspectable var leftPadding : CGFloat = 0 {
didSet {
updateView()
}
}
#IBInspectable var cornerRadiusOfField : CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadiusOfField
}
}
func updateView() {
if let image = leftImage {
leftViewMode = .always
// assigning image
let imageView = UIImageView(frame: CGRect(x: leftPadding, y: 0, width: 20, height: 20))
imageView.image = image
var width = leftPadding + 20
if borderStyle == UITextBorderStyle.none || borderStyle == UITextBorderStyle.line {
width += 5
}
let view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: 20)) // has 5 point higher in width in imageView
view.addSubview(imageView)
leftView = view
} else {
// image is nill
leftViewMode = .never
}
}
func updateRightView() {
if let image = rightImage {
rightViewMode = .always
// assigning image
let imageView = UIImageView(frame: CGRect(x: rightPadding, y: 0, width: 20, height: 20))
imageView.image = image
var width = rightPadding - 20
if borderStyle == UITextBorderStyle.none || borderStyle == UITextBorderStyle.line {
width -= 5
}
let view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: 20)) // has 5 point higher in width in imageView
view.addSubview(imageView)
rightView = view
} else {
// image is nill
rightViewMode = .never
}
}
}
Check below code:
#IBInspectable var leftSideImage:UIImage = UIImage() {
didSet {
leftView = UIImageView.init(image: leftSideImage)
leftViewMode = .always
}
}
#IBInspectable var rightSideImage:UIImage = UIImage() {
didSet {
rightView = UIImageView.init(image: rightSideImage)
rightViewMode = .always
}
}
Output:
An alternate way you can do, In storyboard add a button and add constraints as follows
Button trailing edge = textfield trailing edge
Button (vertical) centre = textfield (vertical) centre
give fix height and width to button
same you can do in right side.
Button leading edge = textfield leading edge
[]_______________[]
I found this example of a dynamic view of UIButtons at: http://helpmecodeswift.com/advanced-functions/generating-uibuttons-loop
I inserted this code into my app but unfortunately I can't find a way to set a tag for each buttons.
My target is to know which button is tapped, count the amount of clicks for each button and save this information into my DB.
override func viewDidLoad() {
super.viewDidLoad()
var arrayOfVillains = ["santa", "bugs", "superman", "batman"]
var buttonY: CGFloat = 20
for villain in arrayOfVillains {
let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
buttonY = buttonY + 50 // we are going to space these UIButtons 50px apart
villainButton.layer.cornerRadius = 10 // get some fancy pantsy rounding
villainButton.backgroundColor = UIColor.darkGrayColor()
villainButton.setTitle("Button for Villain: \(villain)", forState: UIControlState.Normal) // We are going to use the item name as the Button Title here.
villainButton.titleLabel?.text = "\(villain)"
villainButton.addTarget(self, action: "villainButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(villainButton) // myView in this case is the view you want these buttons added
}
}
func villainButtonPressed(sender:UIButton!) {
if sender.titleLabel?.text != nil {
println("You have chosen Villain: \(sender.titleLabel?.text)")
} else {
println("Nowhere to go :/")
}
}
}
So how it is possible to set and get the tag for/from each button in code (in this example)?
Thank you in advance!
You can use enumerated array to access indexes of the current item in iteration and use that as a tag:
override func viewDidLoad() {
super.viewDidLoad()
var arrayOfVillains = ["santa", "bugs", "superman", "batman"]
var buttonY: CGFloat = 20
for (index, villain) in arrayOfVillains.enumerated() {
let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
buttonY = buttonY + 50 // we are going to space these UIButtons 50px apart
// set the tag to current index
villainButton.tag = index
villainButton.layer.cornerRadius = 10 // get some fancy pantsy rounding
villainButton.backgroundColor = UIColor.darkGrayColor()
villainButton.setTitle("Button for Villain: \(villain)", forState: UIControlState.Normal) // We are going to use the item name as the Button Title here.
villainButton.titleLabel?.text = "\(villain)"
villainButton.addTarget(self, action: "villainButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(villainButton) // myView in this case is the view you want these buttons added
}
}
}
#objc func villainButtonPressed(sender:UIButton!) {
// tag of pressed button
print(">>> you pressed button with tag: \(sender.tag)")
if sender.titleLabel?.text != nil {
println("You have chosen Villain: \(sender.titleLabel?.text)")
} else {
println("Nowhere to go :/")
}
}
You should use an integer-based for loop for this.
for i in 0 ..< arrayOfVillains.count {
let villain = arrayOfVillains[i]
let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
villainButton.tag = i
}
You can do as follows,
var buttonTag = 1
for villain in arrayOfVillains {
let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
villainButton.tag = buttonTag
buttonTag += 1
}
this is my code
class ViewController: UIViewController {
#IBOutlet weak var d: UIScrollView!
let numberOfButtons = 50
override func viewDidLoad() {
super.viewDidLoad()
for index in 0..<self.numberOfButtons {
let frame1 = CGRect(x: 20 + index, y: 20 + index, width: 45, height: 45 )
let button = UIButton()
button.frame = frame1
button.titleLabel?.text = "asdfasdf"
self.d.addSubview(button)
}
}
pretty simple, when i run the simulation, i can't see any button in my scroll view.
i am doing this because i need just to have a scroll view that has more items than the simulate can have, just to check the scroll thing
You just need to set the background color (or the text color), right now they're both white, so it looks like they aren't being drawn. Your code would look like this:
#IBOutlet weak var d: UIScrollView!
let numberOfButtons = 50
override func viewDidLoad() {
super.viewDidLoad()
for index in 0..<self.numberOfButtons {
let frame1 = CGRect(x: 20, y: 20 + (index * 45), width: 45, height: 45 )
let button = UIButton(frame: frame1)
button.setTitle("asdfasdf", forState: .Normal)
button.backgroundColor = UIColor.blackColor()
self.d.addSubview(button)
}
}
I've also changed the frames a bit so they'll draw in a straight line down the scroll view. Also, your view will not scroll unless you set the contentSize correctly, so you'll need to throw in a line immediately after the for-loop like this: self.d.contentSize.height = CGFloat(45*numButtons)
I have a weird issue that I just found out when I re-installed and launched my project first time on Simulator
My main ViewController has a collection view that will show a list of recipes. After a recipe is added and navigated back to this screen, the list will reflect the update.
There is also a ScrollView that appears when recipe.count > 0 with subview containing buttons (to filter list by category). The scrollview reflect the update on the recipes but it won't work on the very first launch in Simulator.
I've tried reloading collectionview, fetching recipe data on viewDidAppear, viewWillAppear but no luck with the first-time launch.
How can I make sure the first app launch experience is the same with the subsequent launches? Is there any known issue with first-time launch on Simulator that I should know about?
Here is the code that creates buttons and add to ScrollView.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
fetchRecipe()
collection.reloadData()
if recipes.count == 0 {
categoryScrollView.hidden = true
categoryScrollViewHeight.constant = 0
} else {
populateCategoryScrollView()
}
}
func populateCategoryScrollView() {
//Create category sort buttons based on fetched category.name and add to scrollview
if recipes.count > 0 {
var categories: [String] = []
for recipe in recipes {
if let value = recipe.category {
let category = value as! RecipeCategory
categories.append(category.name!)
}
}
var distinctCategories = Array(Set(categories))
var widthStack = 0
if distinctCategories.count != 0 {
for subView in categoryScrollView.subviews {
subView.removeFromSuperview()
}
for i in 0..<distinctCategories.count {
//Pilot button creation to get width
let frame1 = CGRect(x: 0, y: 6, width: 80, height: 40 )
let button = UIButton(frame: frame1)
button.setTitle("\(distinctCategories[i])", forState: .Normal)
button.sizeToFit()
let buttonWidth = Int(button.frame.size.width)
var frame2: CGRect
if i == 0 {
frame2 = CGRect(x: 10, y: 6, width: buttonWidth+20, height: 30 )
} else {
frame2 = CGRect(x: 10 + widthStack, y: 6, width: buttonWidth+20, height: 30 )
}
widthStack += buttonWidth+32
let button1 = UIButton(frame: frame2)
let attributedTitle = NSAttributedString(string: "\(distinctCategories[i])".uppercaseString, attributes: [NSKernAttributeName: 1.0, NSForegroundColorAttributeName: UIColor.blackColor()])
button1.setAttributedTitle(attributedTitle, forState: .Normal)
button1.titleLabel!.font = UIFont(name: "HelveticaNeue", size: 13.0)
button1.layer.borderWidth = 2.0
button1.layer.borderColor = UIColor.blackColor().CGColor
button1.backgroundColor = UIColor.whiteColor()
button1.addTarget(self, action: "filterByCategory:", forControlEvents: UIControlEvents.TouchUpInside)
self.categoryScrollView.addSubview(button1)
}
}
}
}
The first time you enter the application, the recipes.count is 0, so you are hiding the scroll view by the constraint categoryScrollViewHeight.constant = 0. So next time you create a new recipe, the scroll view is still with a zero height. That's why you didn't see the scroll on first time launch.