So I have a list of songs, and when I click on the moreButton the SongDetailCell is presented and the setPlayPauseOnAppeaering() function is called on the selected song.
This function will either set a play or pause icon depending on the if statement. For some reason the pause-icon is not being, even though the correct console logs are being printed. For example, when "playing with spotify sample", the button is not being set to pause, even though it definitely should be. Does anyone know what I am doing wrong here.
class SongList: UICollectionViewCell {
#objc func moreButtonTap(gesture: UITapGestureRecognizer) {
var indexPath = getIndexPathFromRecognizer(gesture: gesture)
let songDetailView = SongDetailCell()
let song = self.songsArray[indexPath.item]
songDetailView.song = song
songDetailView.setPlayPauseOnAppearing()
}
}
class SongDetailCell: UICollectionViewCell {
var song: Song!
let playPauseButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "play-button-large"), for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.tintColor = UIColor.brandBlack()
button.contentMode = .scaleToFill
return button
}()
func setPlayPauseOnAppearing() {
func setPlayButton() {
playPauseButton.setImage(UIImage(named: "play-button-large")?.withRenderingMode(.alwaysTemplate), for: .normal)
}
func setPauseButton() {
playPauseButton.setImage(UIImage(named: "pause-button-large")?.withRenderingMode(.alwaysTemplate), for: .normal)
}
if SongPlayer.shared.doesPreviewExist(spotifyPreviewUrl: spotifyURL, iTunesPreviewUrl: itunesURL) {
if SongPlayer.shared.player.timeControlStatus == .paused {
if SongPlayer.shared.previewUrl == spotifyURL {
setPlayButton()
print("paused with Spotify sample")
} else if SongPlayer.shared.previewUrl == itunesURL {
setPlayButton()
print("paused with iTunes sample")
}
} else if SongPlayer.shared.player.timeControlStatus == .playing {
if SongPlayer.shared.previewUrl == spotifyURL {
setPauseButton()
print("playing with Spotify sample")
} else if SongPlayer.shared.previewUrl == itunesURL {
setPauseButton()
print("playing with iTunes sample")
}
} else {
print("no audio sample")
}
}
}
}
You are creating a button (playPauseButton). But none of your code ever actually puts that button into the interface. Thus you never see it. You are changing the icon just fine, but the button is off in memory somewhere.
The cell is parallel. You are saying
let songDetailView = SongDetailCell()
But that just makes a new cell, off in space somewhere. It is not the cell that actually is present in the interface. So what you do to it has no visible effect (and then it just vanishes in a puff of smoke).
Related
I am building a quote app using TGLParallaxCarousel library in my project. I try to custom the CustomView of TGLParallaxCarouselItem by adding two UIButtons (favButton and shareButton) on it.
screenshot to the quote cards (CustomView) I create
I am able to change the UIButton view based on its state--whether the current quote is faved or not, by doing this:
convenience init(frame: CGRect, number: Int) {
self.init(frame: frame)
currentQuote = quoteData[number]
favButton.tag = number
currentQuote.faved == true ? favButton.setImage(#imageLiteral(resourceName: "fav-on"), for: .normal) : favButton.setImage(#imageLiteral(resourceName: "fav-off"), for: .normal)
}
However I need to be able to turn the fav on and off by clicking the favButton. I tried to connect the favButton directly as an IBAction to the XIB file, tried to addAction to function, but I still can't access the favButton click state.
Please help. What should I do?
UPDATE
I've tried addTarget on favButton. It's not working. My tap is detected as tap on CustomView rather than specifically on favButton.
Here's the detectTap function that fired when I tap anywhere on the CustomView (including on the favButton). This function is within the TGLParallaxCarousel.swift
func detectTap(_ recognizer:UITapGestureRecognizer) {
let targetPoint: CGPoint = recognizer.location(in: recognizer.view)
currentTargetLayer = mainView.layer.hitTest(targetPoint)!
guard let targetItem = findItemOnScreen() else { return }
let firstItemOffset = (items.first?.xDisp ?? 0) - targetItem.xDisp
let tappedIndex = -Int(round(firstItemOffset / xDisplacement))
self.delegate?.carouselView(self, didSelectItemAtIndex: tappedIndex)
if targetItem.xDisp == 0 {
self.delegate?.carouselView(self, didSelectItemAtIndex: tappedIndex)
}
else {
selectedIndex = tappedIndex
}
}
Did you try to use addTarget?
convenience init(frame: CGRect, number: Int) {
self.init(frame: frame)
currentQuote = quoteData[number]
favButton.tag = number
currentQuote.faved == true ? favButton.setImage(#imageLiteral(resourceName: "fav-on"), for: .normal) : favButton.setImage(#imageLiteral(resourceName: "fav-off"), for: .normal)
favButton.addTarget(self, action: #selector(toggle), for: .touchUpInside)
}
#objc fileprivate func toggle() {
currentQuote.faved = !currentQuote.faved
currentQuote.faved == true ? favButton.setImage(#imageLiteral(resourceName: "fav-on"), for: .normal) : favButton.setImage(#imageLiteral(resourceName: "fav-off"), for: .normal)
}
I run into the following. I have created a custom UIView using XIB file. I connected a VC that inherits from UIView and add it to the File Owner property. Connect two buttons with #IBOutlet and connected both buttons to the same #IBAction method, that will switch the background color, to get a boolean effect. By default no button is selected.
In my ViewController.swift file, that is connected with a View Controller in Storyboard, I add it as subview inside my UICollectionView cells.
Based on my two model classes (Messages and PermissionMessage), I determine what the text of the buttons should be.
My wish is to add functions to the buttons, so when button one is clicked, it will fire function A and if button two is selected, function B.
At this point I don't know where I should do this logic, to determine which function should be fired off. Also because I can create different instances of the same BooleanView.xib so I can't add the functions to the file owners class of that XIB file, am I right?
I am trying to get the button click inside the didSelectItemAt method of UICollectionView, but I can't get access to that programmatically added custom UIView called BooleanView().
How and what should be the correct way to add functions to each instance buttons, based on the button that is clicked?
In my example I want to determine for the current instance if the selected button is Manual or GPS. If Manual, it will do something, like print a text in my console, if GPS is selected, I would like to call the initializer for the LocationManager that I stored in a custom separate class/handler.
This function is placed inside the cellForItemAt method of UICollectionView:
if message is PermissionMessage {
let bool = BooleanView()
bool.frame = CGRect(x: 60, y: estimatedFrame.height + 15, width: estimatedFrame.width + 15 + 8, height: bool.frame.height)
let permissionType = (message as! PermissionMessage).getTypeOfPermission()
switch permissionType {
case .camera:
print("Case: camera")
bool.leftButton.setTitle("Yes", for: .normal)
bool.rightButton.setTitle("No", for: .normal)
cell.addSubview(bool)
case .location:
print("Case: location")
bool.leftButton.setTitle("Manual", for: .normal)
bool.rightButton.setTitle("GPS", for: .normal)
cell.addSubview(bool)
}
}
Note: the message variable is the selected array entry of indexPath.row.
The custom UIView class that is added to my BooleanView.xib:
class BooleanView: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet weak var leftButton: UIButton!
#IBOutlet weak var rightButton: UIButton!
//MARK: Initialization
override init(frame: CGRect) {
super.init(frame: frame)
// Load the nib named 'CardView' into memory, finding it in the main bundle.
Bundle.main.loadNibNamed("BooleanView", owner: self, options: nil)
self.frame = contentView.frame
addSubview(contentView)
// Style the custom view
contentView.backgroundColor = UIColor.clear
leftButton.layer.cornerRadius = (leftButton.frame.height / 2)
rightButton.layer.cornerRadius = (rightButton.frame.height / 2)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
#IBAction func pressedButton(_ sender: UIButton) {
if sender.backgroundColor == Constants.BOOL_BUTTON.enabled {
return
}
sender.backgroundColor = Constants.BOOL_BUTTON.enabled
sender.titleLabel?.font = UIFont(name: Constants.FONTS.bold, size: (sender.titleLabel?.font.pointSize)!)
if sender.tag == 0 {
self.rightButton.backgroundColor = Constants.BOOL_BUTTON.disabled
self.rightButton.titleLabel?.font = UIFont(name: Constants.FONTS.regular, size: (self.rightButton.titleLabel?.font.pointSize)!)
} else {
self.leftButton.backgroundColor = Constants.BOOL_BUTTON.disabled
self.leftButton.titleLabel?.font = UIFont(name: Constants.FONTS.regular, size: (self.leftButton.titleLabel?.font.pointSize)!)
}
}
}
First you need a way to communicate which, if either, button has been pressed, an enum will work for that:
enum BooleanViewState {
case none
case left
case right
}
Next your BooleanView needs to be able to communicate its state to interested parties. Given that you only care about the state of a given boolean view at specific moments in time the easiest is to use a computed property. Also, your current method of reaching into the view to set button titles isn't the best of practices so we will add an alternative to that as well:
class BooleanView: UIView {
var boolViewState: BooleanViewState {
get {
if leftButton.backgroundColor == Constants.BOOL_BUTTON.enabled {
return .left
} else if rightButton.backgroundColor == Constants.BOOL_BUTTON.enabled {
return .right
} else {
return .none
}
}
}
var buttonTitles = (leftTitle: "", rightTitle: "") {
didSet {
leftButton.setTitle(buttonTitles.leftTitle, for: .normal)
rightButton.setTitle(buttonTitles.rightTitle, for: .normal)
}
}
// The rest of you existing class
}
It is possible you will need to add a didSet to the outlets for the two buttons that gets the titles. The titles might get set before the buttons exist.
Next you need to switch from using a default UICollectionViewCell to a custom subclass that can hold a BooleanView and pass things back and forth:
class BooleanViewCell: UICollectionViewCell {
#IBOutlet private var boolView: BooleanView!
var boolViewState: BooleanViewState {
get {
return boolView.boolViewState
}
}
var buttonTitles = (leftTitle: "", rightTitle: "") {
didSet {
boolView.buttonTitles = buttonTitles
}
}
}
You'll need to change some code in your cellForItemAt method
if message is PermissionMessage {
let permissionType = (message as! PermissionMessage).getTypeOfPermission()
switch permissionType {
case .camera:
print("Case: camera")
cell.buttonTitles = (leftTitle: "Yes", rightTitle: "No")
case .location:
print("Case: location")
cell.buttonTitles = (leftTitle: "Manual", rightTitle: "GPS")
}
}
The last thing to do is read boolState from the cell in didSelectItemAt
guard let cell = collectionView.cellForItem(at: indexPath) as? BooleanViewCell else { return }
let theState = cell.boolState
// React to the state as needed
This does mean that you will need to add a generic UIView to your prototype collection view cell in the storyboard, set its class to BooleanView and connect it to the outlet in BooleanViewCell.
That should take care of it all for you though I did all of this with no compiler so I make no guarantees I haven't mistyped or missed something all together.
I am trying to add functionality to dynamically created buttons. I have created buttons with functionality before but never programatically. Here is my code.
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil
{
print("error")
}
else
{
if let content = data
{
do
{
//download JSON data from php page, display data
let SongArray = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as! [[String]]
print(SongArray)
//Make buttons with JSON array
var buttonY: CGFloat = 20
for song in SongArray {
let songButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
buttonY = buttonY + 50 // 50px spacing
songButton.layer.cornerRadius = 10 //Edge formatting for buttons
songButton.backgroundColor = UIColor.darkGray //Color for buttons
songButton.setTitle("\(song[0])", for: UIControlState.normal) //button title
songButton.titleLabel?.text = "\(song[0])"
songButton.addTarget(self,action: #selector(self.songButtonPressed(_:)), for: .touchUpInside)
DispatchQueue.main.async {
self.view.addSubview(songButton) // adds buttons to view
}
}
}
catch
{
}
}
}
}
task.resume()
}
} //close viewDidLoad
func songButtonPressed(_ sender:UIButton) { // function for buttons
if sender.titleLabel?.text == "\("Song[0]")" {
print("So far so good!!")
}
}
What I ideally want to do is use the function SongButtonPressed to run when any of the buttons are pressed, so far the app just crashes when I click on a button, though they appear just fine. How can I use a function to add functionality to these buttons dynamically
It looks like you are using the UIKit framework, so you can use the .isHidden method or .isEnabled
The hidden method with hide the button and the enabled method will have the button appear but the user will not be able to press the button.
To use the method write the following code.
Button.isHidden = true
Or
Button.is enabled = false
But if you use these methods you need to have a button that will do the opposite (set it to true if it is false or false if it is true) so that the button can be used again.
Try by replacing the following line:
function name must be present in Selector attribute
SongButton.addTarget(self,action: #selector(self.SongButtonPressed(_:)), for: UIControlEvents.touchUpInside)
And most important function SongButtonPressed must be outside viewDidLoad
I want to be able to check if all buttons' background color is UIColor.whiteColor. I'm determining whether a view is in search mode or in normal mode by the button state.
I want to do something similar with the following but contains() checks if array contains certain value. It wouldn't work in my case because UIColor.whiteColor is a property of UIButton.
if contains(categoryScrollView.subviews, UIColor.whiteColor) {
inSearchMode = false
}
Then if I put it in the following way, I do not know how I can make sure all button's background color is white as it will pass validation as soon as any button's background color is white which is not what I need.
for button in categoryScrollView.subviews {
if button.backgroundColor == UIColor.whiteColor() {
inSearchMode = false
}
}
How can I check is background color of all buttons?
I'd enclose this check in a function, like this (note that I'm including a check that each view is a button):
func allWhiteButtons(view: UIView)-> Bool{
for view in view.subViews {
if let button = view as? UIButton {
if button.backgroundColor != UIColor.whiteColor() {
return false
}
}
}
return true
}
var allWhite = true
for button in categoryScrollView.subviews {
if button.backgroundColor != UIColor.whiteColor() {
allWhite = false
}
}
inSearchMode = !allWhite
But IMHO, this is not a good way to do it at all. You should have a code to do state transition and make the buttons white or not white based on this state.
If you don't have a lot of UIButtons and that they are already declared as IBOutlets you can create a function that loops through all your UIButtons :
for button in [yourButton1, yourButton2, yourButton2] {
if button.backgroundColor == UIColor.whiteColor {
// Do something
} else {
// Do something else
}
}
Or you can also loop through all the buttons of your self.view :
for view in self.view.subviews as [UIView] {
if let button = view as? UIButton {
if button.backgroundColor == UIColor.whiteColor {
// Do something
} else {
// Do something else
}
}
}
Add a counter like whiteBtnCount in the loop where you are checking the backgroundcolor.Increment that counter if it matches the color and break the loop once counter reaches the button count. Voila,now you know whether all buttons are white color or not.
var whiteBtnCount: Int = 0
for button in categoryScrollView.subviews {
if button.backgroundColor == UIColor.whiteColor() {
whiteBtnCount += 1
if whiteBtnCount == btnCount { //ensure btnCount variable holds the number of buttons
inSearchMode = false
break
}
}
}
I've implemented an Edit button, which adds a button to my UICollectionViewCell. When I press Edit => Edit turns into "Done" and when i press Done it is supposed to delete the Button.
My Code does allow me to add the buttons, but doesn't hide them.
Any Ideas?
#IBAction func editButton(sender: AnyObject) {
if(editButton.titleLabel?.text as String! == "Edit")
{
editButton.setTitle("Done", forState: UIControlState.Normal)
for item in self.mainCollectionView.visibleCells() as! [SettingsCollectionViewCell] {
let indexpath : NSIndexPath = self.mainCollectionView!.indexPathForCell(item as SettingsCollectionViewCell)!
let cell : SettingsCollectionViewCell = mainCollectionView!.cellForItemAtIndexPath(indexpath) as! SettingsCollectionViewCell
//Close Button
let close : UIButton = cell.collectionViewButton
close.hidden = false
}
}
else
{
editButton.setTitle("Edit", forState: UIControlState.Normal)
self.mainCollectionView?.reloadData()
}}
And cellForRowAtIndexPath:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let collectionCell:SettingsCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("collectionCell", forIndexPath: indexPath) as! SettingsCollectionViewCell
if self.editButton.titleLabel?.text as String! == "Edit"
{
collectionCell.collectionViewButton.hidden = true
}
else if editButton.titleLabel?.text as String! == "Done"
{
collectionCell.collectionViewButton.hidden = false
}
collectionCell.collectionViewButton!.layer.setValue(indexPath.row, forKey: "index")
collectionCell.collectionViewButton!.addTarget(self, action: "deletePhoto:", forControlEvents: UIControlEvents.TouchUpInside)
return collectionCell
}
You need to change the logic in cellForItemAtIndexPath for retrieving the title from the button.
In order to retrieve title text from the button you need to use titleForState(UIControlState) instead of titleLabel?.text.
Here is code snippet for conditional statement in cellForItemAtIndexPath
if self.editButton.titleForState(.Normal) == "Edit" {
collectionCell.collectionViewButton.hidden = true
}
else if self.editButton.titleForState(.Normal) == "Done" {
collectionCell.collectionViewButton.hidden = false
}
I think the best strategy here would be to create a button on the main storyboard and check 'hidden' on the button's context menu.
To change a button's text: create an outlet (yes, an outlet) for the button.
#IBOutlet var editbutton: UIButton!
Then to unhide it:
editbutton.hidden = false
(you can put this inside an if statement if desired)
Inside an action of the button (you must assign the button to an action as well) do this to hide the button if it has already been pressed into "Done":
if editbutton.title == "Done" && editbutton.hidden == false {
editbutton.hidden = true
}
Then, to change the text of the button when it still says edit:
if editbutton.title == "Edit" {
editbutton.title = "Done"
}
The actions must be done in this order, or else the button will turn into "Done" and not be hidden, so the computer will go on and hide it before the user presses it a second time.