I'm trying to save button image inside button view. Here is briefly what my code looks like:
I have a UITableView with button in it. Whenever I press the button the image changes. I change the image using this code:
First I use:
cell.checkmarkButton.addTarget(self, action:
#selector(subscribeTapped(_:)), for: .touchUpInside)
to recognize when the image is tapped. Then I use:
#objc func subscribeTapped(_ sender: UIButton) {
selectedButton = String(sender.tag)
if let ButtonImage = sender.image(for: .normal),
let Image = UIImage(named: "WhiteCheckMarkButton"),
ButtonImage.pngData() == Image.pngData()
{
sender.setImage( UIImage.init(named: "GreenCheckMarkButton"), for: .normal)
} else {
sender.setImage( UIImage.init(named: "WhiteCheckMarkButton"), for: .normal)
}
Inside my subscribeTapped function to change the image. All good it changes the image but I can't seem to find out how to save once the image has changed. It seems really confusing to me. I can definitely do it if the image is not in a tableView using UserDefaults. But inside a tableView I have no idea what should I do.
I'm making an extremely simple iOS app. I press a button and the image changes. The problem I'm running into is every time I press the button, it's like the button goes to the bottom of the phone simulator and lays on top of the button I originally pressed.
Also the image only stays for a split second before disappearing. (I'm assuming these actions go hand in hand).
Here is the code if anyone is able to help.
import UIKit
class ViewController: UIViewController {
#IBAction func generateHero(_ sender: UIButton) {
//list of Images in array
let image : NSArray = [ UIImage(named: "batman.jpg")!,
UIImage(named: "the-flash.jpg")!,
UIImage(named: "Deadpool.jpg")!,
UIImage(named: "green-arrow.jpg")!,
UIImage(named: "iron-man.jpg")!]
//random image generating method
let imagerange: UInt32 = UInt32(image.count)
let randomimage = Int(arc4random_uniform(imagerange))
let generatedimage: AnyObject = image.object(at: randomimage) as AnyObject
self.heroImage.image = generatedimage as? UIImage
}
#IBOutlet weak var heroImage: UIImageView!
}
You are doing a lot of unnecessary conversions in random image generation, if you have used Array instead of NSArray you could use .randomElement() function that returns random element of array. So you would just do
self.heroImage.image = image.randomElement()
I am attempting something relatively simple. I have a UIButton that loads with an image:
#IBOutlet var peer5Outlet: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
peer5Outlet.enabled = true
var img = UIImage(named: "camera")
peer5Outlet.setImage(UIImage(named: "camera"),forState: UIControlState.Normal) }
Once it is loaded, I simply want to update that image by calling a method:
func updateButtonImage() {
peer5Outlet.imageView!.image = UIImage(named: "connected")!
print("peer5Outlet.imageView!.image = \(peer5Outlet!.imageView!.image!)")
}
However, I am still unable to update the image; the original image never changes in the view.
Perhaps I am missing some sort of view reload method?
Update your updateButtonImage function to
func updateButtonImage() {
peer5Outlet.setImage(UIImage(named: "connected"), forState: .Normal)
print("peer5Outlet.imageView!.image = \(peer5Outlet!.imageView!.image!)")
}
Try
peer5Outlet.setBackgroundImage(UIImage(named: "connected"), forState: UIControlState.Normal)
Edit: In order to use the setImage method, you need to change the button's type to "Custom"...
You need to use the main thread whenever you're touching UIImage on UI
GCD - main vs background thread for updating a UIImageView
func updateButtonImage() {
dispatch_async(dispatch_get_main_queue()) {
// update image
}
}
If Too long, my problem is : When i click on the play of button of a cell and want to change the image of the pressed play button, some other cell's play button change too.
I'm creating an app where you can get Music preview and play them.
To display this, I use an UITableView with custom UITableViewCell containing a play button and some other things.
That's how it's looking.
So when i click on a Cell Play button, the right track is played but here's the problem : I try to change the backgroundImage of my button but other buttons image are changed too... And if I scroll my button even get back to the image it was using before I make the change...
The cell correctly shows the new image
But some other do it too...
And if I scroll up, my new image is remplaced by the older one...
Here's my function code :
func getPreview(sender : AnyObject){
var positionButton = sender.convertPoint(CGPointZero, toView: self.feedTable)
var indexPath = self.feedTable.indexPathForRowAtPoint(positionButton)
var rowIndex = indexPath!.row
var cell = feedTable.cellForRowAtIndexPath(indexPath!)
var bouton: UIButton = cell?.valueForKey("postPlay") as! UIButton
if sender.title == "Mettre l'extrait en pause" {
mediaPlayer.pause()
sender.setTitle("Jour l'extrait", forState: UIControlState.Normal)
var playImage = UIImage(named: "playIcon")
sender.setImage(playImage, forState: UIControlState.Normal)
}
if rowIndex == bouton.tag{
var songLink = post[rowIndex].valueForKey("previewLink") as! String
let url = NSURL(string: songLink)
mediaPlayer.contentURL = url
mediaPlayer.play()
bouton.setTitle("Mettre l'extrait en pause", forState: UIControlState.Normal)
var pauseImage = UIImage(named: "pauseIcon")
bouton.setImage(pauseImage, forState: UIControlState.Normal)
}
}
Could you help me to find what's wrong ? Thank you !
UITableViewCells are reused, not stored offscreen until you need one for the same index path again. So each time a cell is displayed, -tableView:cellForRowAtIndexPath: is called, and you're responsible for completely setting up the cell. Every time. You can't assume a state you've previously set up for a cell at that index path is still intact.
Is there a way to change the button titles on the SLComposeServiceViewController? I tried to change the bar button items on the navigation item, but those aren't the right buttons.
Simply accessing from navigationController!.navigationBar does the charm. The following should help.
self.navigationController!.navigationBar.topItem!.rightBarButtonItem!.title = "Save"
I just found a way to do it:
class CustomServiceViewController: SLComposeServiceViewController {
override func viewDidLoad() {
let navigationBar = view.subviews.first?.subviews?.last? as? UINavigationBar
let postButton = navigationBar?.subviews.last? as? UIButton
let cancelButton = navigationBar?.subviews.last? as? UIButton
postButton?.setTitle("Done", forState: .Normal)
}
}
Be warned - it's a fragile solution, based on undocumented internals of SLComposeServiceViewController
The answer by Kasztan no longer works with the latest iOS; here is the latest fragile solution..
class CustomServiceViewController: SLComposeServiceViewController {
override func viewDidLoad() {
let navigationBar = view.subviews.last?.subviews?.last? as? UINavigationBar
let postButton = navigationBar?.subviews[3] as? UIButton
postButton?.setTitle("Done", forState: .Normal)
}
}
EDIT #3: Solution working on iOS 9 and iOS 10 beta
The previous approach stopped working with iOS 9, but the following seems to work again (tested on iOS 9 and 10 beta 2):
1) First, you need to add a UIFont class extension to check if a button font is bold (this, because the Post button is always bold); here's how.
2) Then, in viewDidAppear:, we need the following code (an updated version of the code I wrote in Edit 2):
if let navigationBar = self.navigationController?.navigationBar {
// First, let's set backgroundColor and tintColor for our share extension bar buttons
navigationBar.backgroundColor = UIColor.darkGrayColor()
navigationBar.tintColor = UIColor.whiteColor()
if let navBarSubviews = navigationBar.subviews as? [UIView] {
for eachView in navBarSubviews {
if let navBarButton = eachView as? UIButton {
// Second, let's set our custom titles for both buttons (Cancel and Post); checking for the title wouldn't work for localized devices, so we check if the button is bold (Post) or not (Cancel) via the UIFont class extension above.
let buttonFont : UIFont? = navBarButton.titleLabel?.font
if buttonFont?.isBold == true {
navBarButton.setTitle("Save", forState: .Normal)
} else {
navBarButton.setTitle("Cancel", forState: .Normal)
}
}
}
}
}
Of course, this works now, but it will probably break again in the future...
EDIT #2: I made it work on a device with iOS 8.4 :)
Turns out I was wrong, after spending an unreasonable amount of time on this I've been able to both change the color of the buttons and their text.
Here's my code, that needs to be put inside ViedDidAppear() (if you place it in viewDidLoad() it won't work!):
if let navigationBar = self.navigationController?.navigationBar {
// First, let's set backgroundColor and tintColor for our share extension bar buttons
navigationBar.backgroundColor = UIColor.darkGrayColor()
navigationBar.tintColor = UIColor.whiteColor()
if let navBarSubviews = navigationBar.subviews as? [UIView] {
for eachView in navBarSubviews {
if let navBarButton = eachView as? UIButton {
// Second, let's set our custom titles for both buttons (Cancel and Post); checking for the title wouldn't work on localized devices, so we check if the current button is emphasized (Post) or not (Cancel) via an UIFontDescriptor.
let fontDescriptor : UIFontDescriptor? = navBarButton.titleLabel?.font.fontDescriptor()
if let descriptor = fontDescriptor {
let fontAttributes : NSDictionary = descriptor.fontAttributes()
var buttonFontIsEmphasized : Bool? = fontAttributes["NSCTFontUIUsageAttribute"]?.isEqualToString("CTFontEmphasizedUsage")
if buttonFontIsEmphasized == true {
navBarButton.setTitle("Save", forState: .Normal)
} else {
navBarButton.setTitle("Cancel", forState: .Normal)
}
}
}
}
}
}
Still, I'm not sure this should be done on a shipping app nor it would pass App Review (it should, though, because it doesn't mess with private APIs).
Also, it should be noted that this could break anytime, even though it shouldn't be as easily breakable as the previous solutions (it iterates through the subviews and attempts downcasting them, so a small change in the view hierarchy shouldn't render it useless); my expectations is that, even if in the future it stops working, it shouldn't crash the Share Extension.
Original answer
I believe what you (and I) want to do is not possible anymore, possibly by design. Here's why:
Inspired by #Kasztan and #Paito answers, I tried this in viewDidLoad() of my ShareViewController:
for eachView in view.subviews {
println("1")
for eachSubView in eachView.subviews {
println("2")
if let navigationBarView = eachSubView as? UINavigationBar {
println("3")
for eachNavBarSubView in navigationBarView.subviews {
println("4")
if let navBarButton = eachNavBarSubView as? UIButton {
println("5")
println(navBarButton.titleForState(.Normal))
navBarButton.setTitleColor(UIColor.redColor(), forState: .Normal)
navBarButton.setTitle("My text", forState: .Normal)
navBarButton.tintColor = UIColor.redColor()
navBarButton.setNeedsLayout()
navBarButton.layoutIfNeeded()
}
}
}
}
}
Not that I believe something like this should ship in an app, but as a proof of concept this should have worked and, in theory, should be a bit less breakable with future releases of the OS.
Except, it didn't work for me on iOS 8.4: I see all the checkpoint messages logged, from 1 to 5, some of them multiple times (as it should be, since the code tries every possible subview).
The "5" message is logged twice, which makes sense since it means that it successfully downcast both the buttons, Cancel and Post, but not the text nor the color is changed from the default.
My conclusion is that something in Apple's code prevents us to change the appearance of those buttons.
Of course, if anyone finds a solution, I'd be glad to downvote my own answer (if it can be done, I'm note sure) ;)
EDIT #1: One last check, I logged the button title too, after the buttons downcast (5), and yes, I got Optional("Cancel") and Optional("Post") in the console, so this solution gets the right buttons, but they can't be edited.