Swift: Creating an array of UIImage - ios

Using Swift, I'm trying to create an array of UIImage objects for a simple animation. Contextual help for animationImages reads, "The array must contain UI Image objects."
I've tried to create said array as follows, but can't seem to get the syntax correct:
var logoImages: UIImage[]
logoImages[0] = UIImage(name: "logo.png")
This throws:
! Variable logoImages used before being initialized
Then I tried
var logoImages = []
logoImages[0] = UIImage(named: "logo.png")
Which throws:
! Cannot assign to the result of this expression
I've checked the docs here, but the context isn't the same:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html

You have two problems (and without a regex!)
1. You aren't creating an array. You need to do:
var logoImages: [UIImage] = []
or
var logoImages: Array<UIImage> = []
or
var logoImages = [UIImage]()
or
var logoImages = Array<UIImage>()
2. If you want to add new objects to an array, you should use Array.append() or some of the equivalent syntactic sugar:
logoImages.append(UIImage(named: "logo.png")!)
or
logoImages += [UIImage(named: "logo.png")!]
or
logoImages += [UIImage(named: "logo.png")!, UIImage(named: "logo2.png")!]
You need to append to the array because (excerpt from docs):
You can’t use subscript syntax to append a new item to the end of an
array. If you try to use subscript syntax to retrieve or set a value
for an index that is outside of an array’s existing bounds, you will
trigger a runtime error. However, you can check that an index is valid
before using it, by comparing it to the array’s count property. Except
when count is 0 (meaning the array is empty), the largest valid index
in an array will always be count - 1, because arrays are indexed from
zero.
Of course you could always simplify it when possible:
var logoImage: [UIImage] = [
UIImage(named: "logo1.png")!,
UIImage(named: "logo2.png")!
]
edit: Note that UIImage now has a "failable" initializer, meaning that it returns an optional. I've updated all the bits of code to reflect this change as well as changes to the array syntax.

You are declaring the type for logoImages but not creating an instance of that type.
Use var logoImages = UIImage[]() which will create a new array for you.
...and then after creating a new empty Array instance, as described in the answer by #Jiaaro you can't use subscripting to add to an empty array

var image : UIImage = UIImage(named:"logo.png")
var logoImages = [image]

Related

Why does randomElement() return ?? and not?

I'm learning Swift and some iOS development. Here is my code which I'm trying to figure out why XCode is complaining.
private func rollMyDice() {
let diceRollImages = [
UIImage(named: "DiceOne"),
UIImage(named: "DiceTwo"),
UIImage(named: "DiceThree"),
UIImage(named: "DiceFour"),
UIImage(named: "DiceFive"),
UIImage(named: "DiceSix")
]
diceOneImageView.image = diceRollImages.randomElement()
diceTwoImageView.image = diceRollImages.randomElement()
}
So in this case, diceOneImageView.image = diceRollImages.randomElement() will complain that it cannot assign UIImage?? to UIImage?.
ImageView.image is of type UIImage?. What I don't understand is why is randomElement() here is returning UIImage??.
What does UIImage?? mean? I thought ?? was the nil coalescsing operator, so I'm not sure why it's part of some return type.
Also reading the documentation on randomElement(), it should return ?. So in this case, I would expect diceRollImages.randomElement() to return UIImage? which should suit diceOneImageView.image.
What is happening here? I know I can fix it by using ! or using nil coalescing etc. to make it work. Just don't get what's going on.
Note that UIImage(named:) is a failable initialiser, and so the expressions UIImage(named: "DiceOne") etc are of type UIImage?. This makes the array diceRollImages of type [UIImage?]. Each Element of the array is UIImage?, alternatively written as Optional<UIImage>.
As you may know, Optional is just an enum with two cases, .some and .none (aka nil). Each element of the array can be:
.some(image), if a UIImage is created successfully from the name
.none, if there is no images with that name
randomElement is declared to return Element? (Optional<Element>), because the array could have no elements, and hence, cannot give you a random element. randomElement returns:
.none, when the array is empty
.some(elementOfTheArray), when the array is non-empty and elementOfTheArray is a random element in array.
Recall that Element is Optional<UIImage> in the case of diceRollImages, and that the array elements (elementOfTheArray) could have values .some(image) or .none.
Therefore, we can say that randomElement returns a value of type Optional<Optional<UIImage>>, aka UIImage??, and it could be one of 3 things:
.none when the array is empty
.some(.some(image)) when the array is non-empty and a successfully-created image in the array is randomly selected
.some(.none) when the array is non-empty and a unsuccessfully-created image in the array is random selected
Since you are hardcoding the array of images, you know that the array is not empty, and so it is safe to force-unwrap the outer layer of the optional:
diceOneImageView.image = diceRollImages.randomElement()!
The answer has been given already in the comments: UIImage(named:) returns (optional) UIImage? and the result of calling randomElement() on an optional is a double optional ??
This is a good example where force unwrapping is welcome.
The images are part of the application bundle which is immutable at runtime and the app is useless if one of them is missing.
Declare diceRollImages
let diceRollImages = [
UIImage(named: "DiceOne")!,
UIImage(named: "DiceTwo")!,
UIImage(named: "DiceThree")!,
UIImage(named: "DiceFour")!,
UIImage(named: "DiceFive")!,
UIImage(named: "DiceSix")!
]
If the code crashes nevertheless it reveals a design mistake which can be fixed immediately.
You can even force unwrap randomElement()! because the array is a constant and is clearly not empty.

Why <Element> is not needed when creating default value array in Swift?

The code below is excerpted from The Swift Programming Language (Swift 4.2) of Apple documentation.
Now consider this: there are 3 ways to create an empty array in Swift.
First:
var list1 = Array<String>()
Second:
var list2: [String] = []
Third:
var list3 = [String]()
In order to create a default value array, we make use of repeating: and count:
var list4 = Array(repeating: "Apples", count: 3)
The above array is created using the first form of array creation, however, why it is not needed to declare the <Element> next to Array initializer.
Trying to create a normal empty array without specifying which type inside the <Element> would trigger a runtime error.
var list5 = Array("Apples") //A,p,p,l,e,s
var list6 = Array("Apples", "Oranges") //return an error
Why this happens?
Because it knows the type of the first item. Since Arrays only contain objects of a single type, of it can get that information from the first item, then it is enough.
Following your comment:
I think you are getting confused with the first item. "Oranges" is an array of [Character] so when you use Array("Oranges") you are creating an array of characters. For your third item, rather than passing in two values, which an Array doesn't know how to deal with, pass in an array like this: let list6 = Array(["Apples", "Oranges"])

Ambiguous use of 'mutableCopy()' Swift3

I tried to update Swift 3 and I got the following error :
Ambiguous use of 'mutableCopy()'
Before update to swift 3. It runs well.
Swift 2.3
NSUserDefaults.standardUserDefaults().objectForKey("listsavednews")?.mutableCopy() as! NSMutableArray
Swift 3.0
(UserDefaults.standard.object(forKey: "listsavednews")? as AnyObject).mutableCopy() as! NSMutableArray
I found that mutableCopy in Swift3 return Any that doesnt have method mutableCopy() so that it needs to cast to AnyObject.
Any helps thanks.
I dont know why I can't comment.
Thanks all, I'll be using :
UserDefaults.standard.mutableArrayValue(forKey: "listsavednews")
mutableCopy is an Objective-C method from NSObject. There's little reason to use it in Swift 3.
Since you are dealing with UserDefaults and mutableCopy, you must be dealing with either an array or dictionary. Or it could be a string.
The proper way to do this in Swift 3 is to use the proper UserDefaults method to get an array or dictionary. And assign the result to a var. That combination will give you a mutable array or mutable dictionary.
var someArray = UserDefaults.standard.array(forKey: "somekey")
or:
var someDictionary = UserDefaults.standard.dictionary(forKey: "somekey")
In the two above cases, you end up with an optional since there might not be any data for the given key. And you also get a non-specific array or dictionary which isn't ideal. It would be better to cast the result to the appropriate type.
Let's say you have an array of strings and you want an empty array if there is nothing currently in user defaults. You can then do:
var someArray = UserDefaults.standard.array(forKey: "somekey" as? [String]) ?? []
Adjust as necessary if the array contains something other than String.
If you actually have a dictionary, the code would be similar.
var someDictionary = UserDefaults.standard.dictionary(forKey: "somekey") as? [String:String] ?? [:]
If your original object is just a string, then you could do:
var someString = UserDefaults.standard.string(forKey: "somekey") ?? ""

Creating and populating an empty Array

I'm actually learning swift in order to develop iOS apps. I'd like, as an exercise, to create and populate an empty array, that would be filled by using a textfield and a button on the storyboard.
var arr = []
// When button is pressed :
arr.append(textfield.text)
XCode tells me that the append method is not a method of NSArray. So I have used the addObject one, but it is still not correct as the arr variable contains nil.
So here are my three questions :
Is it possible to create an empty array, and if so, how to populate it ?
Sometimes, in my ViewController, when I create a non-empty array, the append method is apparently not valid, and I don't understand why..
Finally, why even though I use the syntax :
var arr = [1] // For example
The arr object is NSArray object and not a NSMutableArray object, making it impossible to add/remove any object that is contained in it?
I hope my questions are clear, if not I'll upload more code of what I'm trying to build,
thank you for your answers !
Try to define your array as a String array, like this:
var arr: [String] = []
And then append to your list, either by:
arr.append("the string")
or
arr += ["the string"]
Empty array can be created using the following syntax.
var emptyArray = [String]()
emptyArray.append("Hi")
see this
You can use following also to add elements to your array.
//append - to add only one element
emptyArray.append("Hi")
//To add multiple elements
emptyArray += ["Hello", "How r u"]
emptyArray.extend(["am fine", "How r u"])
//Insert at specific index
emptyArray.insert("Who r u", atIndex: 1)
//To insert another array objects
var array1 = ["who", "what", "why"]
emptyArray.splice(array1, atIndex: 1)

How does one create a mutable copy of an immutable array in swift?

Now that Swift's Array's are truly immutable thanks to full value semantics, how can I create an mutable copy of an immutable array? Similar to Obj-C mutableCopy(). I can of course downcast the array to an NSArray and use mutableCopy() but don't want to use NSArray because it does not strictly typed.
I have a toolbar which has items from the storyboard. I want to remove an item from the toolbar and use toolbar.setItems. I wanted to do it without casting as a NSArray, because none of these functions take NSArrays, they take [AnyObject].
Obviously now when I call removeAtIndex() it does not work, which is correct. I just need a mutableCopy
Simply assigning to var does not work for me and give 'Immutable value of type [AnyObject]'
var toolbarItems = self.toolbar.items
toolbarItems.removeAtIndex(2) //Immutable value of type [AnyObject]
I am using Beta 3
The problem is that self.toolbar.items is an implicitly unwrapped optional (of type [AnyObject]!) and they are always immutable. When you assign to the variable toolbarItems without explicitly stating its type, it too becomes an implicitly unwrapped optional, and thus is immutable as well.
To fix this do either:
var toolbarItems:[AnyObject] = self.toolbar.items
toolbarItems.removeAtIndex(2)
Or:
var toolbarItems = self.toolbar.items as [AnyObject]
toolbarItems.removeAtIndex(2)
Update
As of Xcode 6 Beta 5, you can update collections that are stored in optional variables, so the original code now works:
var toolbarItems = self.toolbar.items
toolbarItems.removeAtIndex(2)
Arrays are value types (struct), so they are passed around by value and not by reference.
That said, if you create a variable of array type and assign it the immutable array, a copy of the immutable array is actually created and assigned to it - and of course that copy has no relationship with the original immutable array (besides having the same values at the time it is created).
let immutable = [1, 2, 3]
//immutable[0] = 1 // Fails, ok
var mutable = immutable
mutable[0] = 5
In your case, you are accessing an immutable array which is an NSArray of AnyObjects (see documentation). You can use it as an Array in swift, make a copy of it and modify as follows:
let immutable : NSArray = [ 1, 2, 3 ]
//immutable[0] = 1 // Fails, ok
var mutable : [AnyObject] = immutable
mutable.removeAtIndex(1) // mutable now is [1, 3]
mutable[0] = 7 // mutable now is [7, 3]
After you're done with your changes, you can assign to the items property
It is as simple as declaring a var with your array.
var items = toolbar.items
Now you can change items and then reassign to the toolbar.
toolbar.items = items
Note that you can cannot (as of Beta 3) alter the elements of an "immutable" array declared with let as well. Just the length of the array is was fixed, which is why you cannot remove items.
However, according to Apple's documentation of UIToolbar, the items array is already mutable.
SWIFT
var items: [AnyObject]!
Tested + works:
var mutable : [UIBarButtonItem] = []
for button in toolbar.items {
mutable += button as UIBarButtonItem
}
mutable.removeAtIndex(2)
toolbar.setItems(mutable, animated: true)
TO REMOVE AN OBJECT FROM PARTICULAR INDEX OF AN ARRAY.
let fullArray : NSArray = Userdefaults().value(forKey: "YOUR_ARRAY_STRING") as! NSArray
var mutableArray : [AnyObject] = fullArray as [AnyObject]
mutableArray.remove(at: INDEX_TO_REMOVE) //Eg: mutableArray.remove(at: 0)
mutableArray.append(ARRAY_TO_APPEND)
In Beta3 constant arrays are completely immutable while variable arrays are entirely mutable. So just change let array:to var array: and then verify your code

Resources