Is there a way to compare button images in Swift/iOS - ios

I've looked around and I don't think there's a way to do this, but what I'm looking for is the ability to compare button images. I have a situation where I want to see if the button is set to a certain image before I change it. The pseudocode would be something like
if (myCell.followButton.image == UIImage(named: "")) {
//do something here
}

Updated for iOS 8 thanks to kakubei
UIButton has a property called currentImage, so use that to compare the images:
iOS 8+
if myCell.followButton.currentImage.isEqual(UIImage(named: "yourImageName")) {
//do something here
}
iOS 7-
if (myCell.followButton.currentImage == UIImage(named: "yourImageName")) {
//do something here
}
A better way to achieve this functionality would be to keep track of the button's selected state and change its image based on that. That would make it more flexible if you ever change the name of the image.

Matt Cooper's solution won't work in iOS 8 because Apple has changed how images are cached. You now need to use .isEqual
As in:
// assuming both button and newButton are UIImage instances
if button.currentImage!.isEqual(newButton) {
...
From Apple Docs:
As of iOS 8, you can no longer rely on pointer equality to compare
cached UIImage objects as the caching mechanism may not return the
same UIImage object, but will have cached image data separately. You
must use isEqual: to correctly test for equality.
The downside of this is that it won't work in iOS 7! I don't know of a way that will work in both but I'm hoping there is, I'm searching for it myself :)

It can be done in following way:
if let btnImage = sender.image(for: .normal),
let Image = UIImage(named: "firstImage.png"), UIImagePNGRepresentation(btnImage) == UIImagePNGRepresentation(Image)
{
sender.setImage(UIImage(named:"secondImage.png"), for: .normal)
}
else
{
sender.setImage( UIImage(named:"firstImage.png"), for: .normal)
}

On iOS 13 #Matt Cooper's answer does not work, I'm not sure but other infos are included in the name of the image (maybe changes in the API since 2015), so my comparison always returns false:
UIImage: 0x28297e880 named (main: Icon-camera-alt) {20, 20}
I've used this in my case:
myButton.currentImage?.description.contains("camera-alt")

Sure, if you keep the image references you can just do a straight compare. Otherwise, I am not aware of a way.

Related

Image Literals, Arrays and randomizing in Swift. Why does this method work but the other doesn't

Creating a dice roll app following Angela Yu's App Brewery bootcamp on Udemy. She uses image literals but those seem to have been deprecated on newer versions of Xcode.
I get the error: Cannot assign value of type 'UIImage??' to type 'UIImage?' Whenever I try to use diceArray.randomElement()
However, diceArray[Int.random(in:0...5] seems to work fine. My suspicion is maybe to use .randomElement() I need to use imageView but new to Swift so not sure if this is correct or what the difference even is. Any explanation would be appreciated. Thanks.
#IBAction func rollButtonPressed(_ sender: UIButton) {
var diceArray = [UIImage(named: "DiceOne"),UIImage(named: "DiceTwo"),UIImage(named: "DiceThree"),UIImage(named:"DiceFour"),UIImage(named:"DiceFive"),UIImage(named:"DiceSix")]
diceImageView1.image = diceArray.randomElement()
diceImageView2.image = diceArray[Int.random(in: 0...5)]
}
UIImage(named: initializer returns UIImage? and randomElement returns an Optional so it's ?? , you can force it
diceImageView1.image = diceArray.randomElement()!
As Sh_Khan pointed out, you have an array of Optionals since UIImage.named(_:) returns an Optional.
I would suggest adding a call to compactMap at the end of your code that declares your array of images:
var diceArray = [UIImage(named: "DiceOne")…].compactMap { $0 }
That will make diceArray an array of UIImages instead of Optional(UIImage) (and strip out any failed attempts to load images.)
Edit:
Alexander pointed out in the comments that this might be a case where force-unwrapping is a good thing.
If you write the code like this:
var diceArray = [
UIImage(named: "DiceOne")!,
UIImage(named: "DiceTwo")!,
UIImage(named: "DiceThree")!,
UIImage(named:"DiceFour")!,
UIImage(named:"DiceFive")!,
UIImage(named:"DiceSix")!
]
That version will also create an array of non-optional UIImages. If you mis-name any of the images, or there are other problems, the above code will crash immediately. Once it runs without crashing, though, you can be confident that the images will continue to load.
The version with compactMap() would simply create an array when any misnamed images were missing.

Getting multiple IF statement clauses to work together

I am building an iOS quiz app and depending on certain key words that appear in the question label I want the background photo to change corresponding to the key word in the question. For example, if the question label contains the word "food" I would want the background picture to always show a picture of an apple. If the question label contains the word "fruit" I also want the background picture to be that same apple as for the key word "food". However when I run my code it only works properly if I use only ONE key word.
//this code works and changes the background picture appropriately
func quizImage() {
if (questionLabel.text?.contains("food"))!
//applePicture is the name of the image
{ questionImage.image = applePicture }
}
However when I try the following ,to use multiple if clauses, the background photo doesn't change at all, even if one of the key words appear in the question label
func quizImage() {
//this code doesn't work and the background photo never changes
if (questionLabel.text?.contains("food"))!,(questionLabel.text?.contains("apple"))!
{ questionImage.image = applePicture}
}
Any help of advice is greatly appreciated!
If you use comma(,) then it will be true only if both the conditions satisfies. Do like this,
if let text = questionLabel.text, (text.contains("food") || text.contains("apple")) {
questionImage.image = applePicture
}
You could wrap those two variants in an array, and use the contains function:
if let text = questionLabel.text, ["food", "apple"].contains(where: { text.contains($0) }) {
questionImage.image = applePicture
}
This will reduce the code duplication, and it's scalable in case that later on you will need to cover more that 2 variants.

Swift: Random number arrays inside images and variables with a for loop

I am creating a game in which, depending on the number of 'swipes' chosen to do, (let's say 3), 3 different patterns show on the screen, one by one. I am working on developing the first pattern.
So I have this:
if (swipes.no_of_swipes) == 3 {
swipeArray = Array<UInt32>(count: 3, repeatedValue: 0)
for i in 0 ..< 3 {
swipeArray[i] = arc4random_uniform(84)}
}
As far as I am aware, this code creates an array with three UInts which can be accessed by doing swipeArray[0], swipeArray[1], and swipeArray[2]. My first question is how long will this swipeArray stay the same? Until the close the view? Should I have a 'refresh button' when the user loses - and if so, how would I make one?
Then I have a property observer. You will notice the for loop, which I am using to keep code concise. I understand that I could do something like x++ somewhere in here so that it will go through each one.
var playBegin: Bool = false{
didSet {
if playBegin == true {
println("\(playBegin)")
var swipes = Menu()
if (swipes.no_of_swipes) == 3 {
for i in 0 ..< 3 {
patternRoom.image = UIImage(named: "pattern\(swipeArray[x])")
//rest of code
}
}
}
The pattern image comes from a set of 84 images named like pattern7 and pattern56. My second question is, how could I code the for loop to go through each swipeArray[x].
Thank you in advance,
Will
how long will this swipeArray stay the same?
This is a bit too open ended. It’ll stay the same until you assign a new value to it, either from this same bit of code or a different part. Only you can know when that will be, by looking at your code.
Since you express an interest in keeping the code concise, here’s a couple of code tips.
You might think about writing your first snippet’s loop like this:
swipeArray = (0..<swipes.no_of_swipes).map { _ in
arc4random_uniform(84)
}
This combines creating a new array and populating the values. By the way, just in case you don’t realize, there’s no guarantee this array won’t contain the same value twice.
It’s also probably better to make swipeArray of type [Int] rather than [UInt32], and to convert the result of arc4random to an Int straight away:
Int(arc4random_uniform(84))
Otherwise the UInt32s will probably be a pain to work with.
For your second for loop, you can do this:
for i in swipeArray {
patternRoom.image = UIImage(named: "pattern\(i)")
// rest of code
}
When writing Swift, usually (but not always), when you find yourself using array[x] there’s a better more expressive way of doing it.

I'm currently checking if UIAlertController exists to determine what OS version is being used. Is there a more efficient way to do this?

I have a delete button on a cell and it was crashing my app in iOS7 but not in iOS8. I later found out the crash was caused by by how deep down I was digging into my view hierarchy.
To make things clearer here is the code that solved the issue:
func didTapDeleteButton(sender: UIButton) {
var cell: HoursOfOperationTableViewCell!
if let gotModernAlert: AnyClass = NSClassFromString("UIAlertController") {
println("UIAlertController can be instantiated")
//make and use a UIAlertController
cell = sender.superview?.superview? as HoursOfOperationTableViewCell
}
else {
println("UIAlertController can NOT be instantiated")
cell = sender.superview?.superview?.superview? as HoursOfOperationTableViewCell
}
As you can see I have to go further down into my view hierarchy to get to my custom cell view in iOS 7.
I was wondering if there was a more efficient way of checking the iOS version?
If I don't check then my app crashes in iOS7 if I leave the code like this:
cell = sender.superview?.superview? as HoursOfOperationTableViewCell
Thanks for your time.
To test if a class is loaded in the current runtime, you could call the following:
var classExistsAndIsSafeToUse = countElements(NSStringFromClass(object_getClass(UIAlertController))) > 0
You should always check for the existence of a particular class over checking the iOS version.
That being said, for checking the iOS version, below is an excerpt from my solution on just that
New in iOS 8 is NSProcessInfo that integrates greatly with Swift and
allows for better semantic versioning checks. Thanks to NSHipster
we can see a clean and canonical source from where the OS version can
be derived.
For minimum deployment targets of iOS 8.0 or above, use NSProcessInfo
operatingSystemVersion or isOperatingSystemAtLeastVersion.
This would yield the following:
let minimumVersion = NSOperatingSystemVersion(majorVersion: 3, minorVersion: 1, patchVersion: 3)
if NSProcessInfo().isOperatingSystemAtLeastVersion(minimumVersion) {
//do some fun stuff
}
For minimum deployment targets of iOS 7.1 or below, use compare with
NSStringCompareOptions.NumericSearch on UIDevice systemVersion.
This would yield:
let minimumVersionString = "3.1.3"
let versionComparison: NSComparisonResult = UIDevice.currentDevice().systemVersion.compare(minimumVersionString,
options: NSStringCompareOptions.NumericSearch)
if versionComparison == .OrderedDescending {
//do some fun stuff
}
EDIT
as a side note, if you see something like this:
cell = sender.superview?.superview? as HoursOfOperationTableViewCell
your code has begun to exhibit whats known as code smell, you should consider implementing your view logic inside of your view, the controllers should not have a deep knowledge into the view hierarchy. Remember, the goal is on writing easy to change code. Consider implementing abstraction, delegation and singular responsibility principled design.
Good luck
You can simply check [[UIDevice currentDevice] systemVersion]. This returns an NSString so you will need to convert to a float if you want to compare.
Another way is using NSFoundationVersionNumber. Declare the following constants in a file where you keep global constants.
let iOS7 = floor(NSFoundationVersionNumber) <= floor(NSFoundationVersionNumber_iOS_7_1)
let iOS8 = floor(NSFoundationVersionNumber) > floor(NSFoundationVersionNumber_iOS_7_1)
And check for the version like this.
if iOS8 {
// Use iOS 8 APIs
} else {
// Fallback for older iOS versions
}

iOS Identifying views in ObjC and Swift

What is the best practice to identify views in iOS?
For instance, you could have a lot of views that aren't worth subclassing because you need to work with just one subview in those views.
I like to use view tags, and for easy identification I would do like so for ObjC:
enum {
kViewTagSomeButton = 10,
kViewTagSomeOtherButton,
}
But to do this in Swift, this becomes quite verbose:
enum ViewTag: Int {
case SomeButton = 10, SomeOtherButton, ...
}
if view.tag == ViewTag.SomeButton.rawValue {
// Do something
}
Or should I use a completely different approach?
Edited
Changed the whole question to a more appropriate context

Resources