How come I'm getting the error "NSArray does not have a member named 'removeAtIndex'. How can I fix this? The error is on the fourth last line. Sorry if my question is stupid, I'm fairly new to programming. I appreciate all the help I get.
import Foundation
let userDefaults = NSUserDefaults.standardUserDefaults()
func isAppAlreadyLaunchedOnce()->Bool{
let defaults = NSUserDefaults.standardUserDefaults()
if let isAppAlreadyLaunchedOnce = defaults.stringForKey("isAppAlreadyLaunchedOnce"){
println("App already launched")
return true
}
else{
defaults.setBool(true, forKey: "isAppAlreadyLaunchedOnce")
println("App launched first time")
return false
}
}
struct newFactBook {
let factsArray = [
"Ants stretch when they wake up in the morning.",
"Ostriches can run faster than horses.",
"Olympic gold medals are actually made mostly of silver.",
"You are born with 300 bones; by the time you are an adult you will have 206.",
"It takes about 8 minutes for light from the Sun to reach Earth.",
"Some bamboo plants can grow almost a meter in just one day.",
"The state of Florida is bigger than England.",
"Some penguins can leap 2-3 meters out of the water.",
"On average, it takes 66 days to form a new habit.",
"Mammoths still walked the earth when the Great Pyramid was being built."]
}
var checkLaunch = isAppAlreadyLaunchedOnce()
var oldFunFactsArray = []
if(checkLaunch == false){
oldFunFactsArray = newFactBook().factsArray
}
else if (checkLaunch == true){
oldFunFactsArray = userDefaults.objectForKey("key") as! NSArray
}
func randomFacts1() -> (String, Int){
var unsignedArrayCount = UInt32(oldFunFactsArray.count)
var unsignedRandomNumber = arc4random_uniform(unsignedArrayCount)
var randomNumber = Int(unsignedRandomNumber)
return (oldFunFactsArray[randomNumber] as! String, randomNumber)
}
oldFunFactsArray.removeAtIndex[randomFacts1().1] //error here
userDefaults.setObject(oldFunFactsArray, forKey:"key")
userDefaults.synchronize()
println(oldFunFactsArray)
We have some problems here:
1 How to invoke a method
removeAtIndex is a method that accepts an Int as parameters. It cannot be invoked this way
removeAtIndex[randomFacts1().1]
instead you should write
removeAtIndex(randomFacts1().1)
2. The type of oldFunFactsArray is NSArray and it's wrong.
Intact when you write this:
var oldFunFactsArray = []
Swift does infer this:
var oldFunFactsArray : NSArray = []
But at this point you have an immutable NSArray so it does not have the removeAtIndex method.
Since you are using Swift I suggest you to declare the var oldFunFactsArray as follow:
var oldFunFactsArray : [String]
if checkLaunch == false {
oldFunFactsArray = newFactBook().factsArray
} else {
oldFunFactsArray = userDefaults.objectForKey("key") as! [String]
}
Please note that here I am declaring a Swift array of String(s). Since I use the var keyword this array will be mutable and we will be able to invoke removeAtIndex later on.
Also note that in the else branch I am force-casting the result of objectForKey to [String]. It will be fine since I see, below, you are writing the oldFunFactsArray in that slot.
Hope this helps.
You need to use NSMutableArray to use this method.
NSArray is not Mutable (can not change its content after it is intialized).
Related
I tried to cast a swift protocol array as any array, but failed.
protocol SomeProtocol: class{
}
class SomeClass: NSObject, SomeProtocol{
}
let protocolArray: [SomeProtocol] = [SomeClass()]
let value: Any? = protocolArray
if let _ = value as? [SomeProtocol]{
print("type check successed") //could enter this line
}
Above code could work as expected.
However, my problem is, I have a lot of protocols, and I don't want to check them one by one. It is not friendly to add new protocol.
Is there any convenience way to do check if above "value" is a kind of array like below?
if let _ = value as? [Any]{
print("type check successed") //never enter here
}
edit:
Inspired by Rohit Parsana's answer, below code could work:
if let arrayType = value?.dynamicType{
let typeStr = "\(arrayType)"
if typeStr.contains("Array"){
print(typeStr)
}
}
But these code seems not safe enough, for example, you can declare a class named "abcArray".
Although we could use regular expression to check if "typeStr" matches "Array<*>", it seems too tricky.
Is there any better solution?
You can use reflection:
if value != nil {
let mirror = Mirror(reflecting: value!)
let isArray = (mirror.displayStyle == .Collection)
if isArray {
print("type check succeeded")
}
}
You can check the type of value using 'dynamicType', here is the sample code...
if "__NSCFArray" == "\(page.dynamicType)" || "__NSArrayM" == "\(page.dynamicType)"
{
print("This is array")
}
else
{
print("This is not array")
}
How can I prevent my app to crash if an array is empty?
var UserVideosInfo = [[String]]()
#IBAction func actionBtn(sender: UIButton) {
userVideoInfo = NSUserDefaults.standardUserDefaults().objectForKey("UserVideos") as! [[String]]
}
If the array is empty userVideosInfo crashes saying:
fatal error: unexpectedly found nil while unwrapping an Optional value
I have tried:
if var userVideoInfoArray:[[String]] = UserVideosInfo {
userVideoInfoArray = NSUserDefaults.standardUserDefaults().objectForKey("UserVideosJSON") as! [[String]]
}
The issue seems to be related to NSUserDefault instead.
With your attempt to retrieve the object with the key "UserVideos", NSUserDefaults might have returned nil. It was then explicitly unwrapped into an array, and that causes the program to crash.
Please verify the existence of userVideoInfo first before proceeding.
var UserVideosInfo: [[String]]?
#IBAction func actionBtn(sender: UIButton) {
userVideoInfoOrNil = NSUserDefaults.standardUserDefaults().objectForKey("UserVideos")
if userVideoInfo = userVideoInfoOrNil as! [[String]] {
//Do stuff with userVideoInfo
} else {
//Value is nil
}
}
You can prevent it by registering the key/value pair as Apple recommends.
In AppDelegate add as soon as possible
let defaults = NSUserDefaults.standardUserDefaults()
let defaultValues = ["UserVideos" : [[String]]()]
defaults.registerDefaults(defaultValues)
The benefit is that UserVideos can never be nil and then you can safely write
userVideoInfoArray = NSUserDefaults.standardUserDefaults().objectForKey("UserVideos") as! [[String]]
Please read the section about registering default values in the documentation
If I use the code below the app its not crashing
if self.Videos.count >0{
Videos = (NSUserDefaults.standardUserDefaults().objectForKey("Videos") as! [[String]])
}
I'm a newbie to swift and believe me I've searched and searched for an answer already. I want to create UISliders that get their values from an array of numbers in Swift. Its a camera app so the example array should be obvious.
#IBAction func isoValueChanged(sender: UISlider) {
let isoArray = ["24", "32", "50","64","80","100","125","160","200","250","320","400","500","640","720","800","1000","1250","1600","1800"] // available iPhone 6s ISO settings I believe
let currentValue = // What do I need?
isoText.text = "\(currentValue)"
}
Even harder would be representing shutter speeds from 1/1000 - 32!
From what I see out there this is not an easy one because there is no mathematical representation to calc from the array. Is it even possible?
I'm not quite sure I understand what you want this for but I'm guessing this is right.
// Global Variable or something like this (accessible from multiple functions)
let isoArray = ["24", "32", "50","64","80","100","125","160","200","250","320","400","500","640","720","800","1000","1250","1600","1800"] // available iPhone 6s ISO settings I believe
func functionThatCreatesTheSliderYo(...) {
slider.minimumValue = 0
slider.maximumValue = isoArray.count
slider.continuous = false
}
#IBAction func isoValueChanged(sender: UISlider) {
// instead of Int(val) you may want to round(val) for a better UI
let currentValue = isoArray[Int(sender.value)]
isoText.text = "\(currentValue)"
}
This works for me
#IBAction func isoValueChanged(sender: UISlider) {
let isoArray = ["24", "32", "50","64","80","100","125","160","200","250","320","400","500","640","720","800","1000","1250","1600","1800"]
let currentValue = isoArray[Int(sender.value)]
isoText.text = "\(currentValue)"
}
So simple as it should be in Swift. Many thanks to Ty. Be sure to see the chat though.
Consider the following silly, simple example:
let arr = ["hey", "ho"]
let doubled = arr.map {$0 + $0}
let capitalized = arr.map {$0.capitalizedString}
As you can see, I'm processing the same initial array in multiple ways in order to end up with multiple processed arrays.
Now imagine that arr is very long and that I have many such processes generating many final arrays. I don't like the above code because we are looping multiple times, once for each map call. I'd prefer to loop just once.
Now, obviously we could handle this by brute force, i.e. by starting with multiple mutable arrays and writing into all of them on each iteration:
let arr = ["hey", "ho"]
var doubled = [String]()
var capitalized = [String]()
for s in arr {
doubled.append(s + s)
capitalized.append(s.capitalizedString)
}
Fine. But now we don't get the joy of using map. So my question is: is there a better, Swiftier way? In a hazy way I imagine myself using map, or something like map, to generate something like a tuple and magically splitting that tuple out into all resulting arrays as we iterate, as if I could say something like this (pseudocode, don't try this at home):
let arr = ["hey", "ho"]
let (doubled, capitalized) = arr.map { /* ???? */ }
If I were designing my own language, I might even permit a kind of splatting by assignment into a pseudo-array of lvalues:
let arr = ["hey", "ho"]
let [doubled, capitalized] = arr.map { /* ???? */ }
No big deal if it can't be done, but it would be fun to be able to talk this way.
How about a function, multimap, that takes a collection of transformations, and applies each one, returning them as an array of arrays:
// yay protocol extensions
extension SequenceType {
// looks like T->U works OK as a constraint
func multimap
<U, C: CollectionType
where C.Generator.Element == Generator.Element->U>
(transformations: C) -> [[U]] {
return transformations.map {
self.map($0)
}
}
}
Then use it like this:
let arr = ["hey", "ho"]
let double: String->String = { $0 + $0 }
let uppercase: String->String = { $0.uppercaseString }
arr.multimap([double, uppercase])
// returns [["heyhey", "hoho"], ["HEY", "HO"]]
Or it might be quite nice in variadic form:
extension SequenceType {
func multimap<U>(transformations: (Generator.Element->U)...) -> [[U]] {
return self.multimap(transformations)
}
}
arr.multimap({ $0 + $0 }, { $0.uppercaseString })
Edit: if you want separate variables, I think the best you can do is a destructure function (which you have to declare n times for each n-tuple unfortunately):
// I don't think this can't be expressed as a protocol extension quite yet
func destructure<C: CollectionType>(source: C) -> (C.Generator.Element,C.Generator.Element) {
precondition(source.count == 2)
return (source[source.startIndex],source[source.startIndex.successor()])
}
// and, since it's a function, let's declare pipe forward
// to make it easier to call
infix operator |> { }
func |> <T,U>(lhs: T, rhs: T->U) -> U {
return rhs(lhs)
}
And then you can declare the variables like this:
let (doubled,uppercased)
= arr.multimap({ $0 + $0 }, { $0.uppercaseString }) |> destructure
Yes this is a teensy bit inefficient because you have to build the array then rip it apart – but that’s really not going to be material, since the arrays are copy-on-write and we’re talking about a small number of them in the outer array.
edit: an excuse to use the new guard statement:
func destructure<C: Sliceable where C.SubSlice.Generator.Element == C.Generator.Element>(source: C) -> (C.Generator.Element,C.Generator.Element) {
guard let one = source.first else { fatalError("empty source") }
guard let two = dropFirst(source).first else { fatalError("insufficient elements") }
return (one,two)
}
What is wrong with your suggestion of tuple?
let arr = ["hey", "ho"]
let mapped = arr.map {e in
return (e + e, e.capitalizedString)
}
How about this, we process 'capitalized' array while we map the 'doubled' array:
let arr = ["hey", "ho"]
var capitalized = [String]()
let doubled = arr.map {(var myString) -> String in
capitalized.append(myString.capitalizedString)
return myString + myString
}
//doubled ["heyhey", "hoho"]
//capitalized: ["Hey", "Ho"]
Getting hugely frustrated trying to translate objective c to swift. I have the following code that works for objective c.
NSMutableArray *path = [NSMutableArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Sequence List" ofType:#"plist"]];
//Shuffle the array of questions
numberSequenceList = [self shuffleArray:path];
currentQuestion = currentQuestion + 1;
if (Round==1) {
//Take first object in shuffled array as the first question
NSMutableArray *firstQuestion = [[NSMutableArray alloc] initWithArray:[numberSequenceList objectAtIndex:0]];
//Find question and populate text view
NSString *string = [firstQuestion objectAtIndex:0];
self.lblNumber.text = string;
//Find and store the answer
NSString *findAnswer = [firstQuestion objectAtIndex:1];
Answer = [findAnswer intValue];
}
But I can't seem to get this to work in swift. I can pull out the contents of the plist using
var path = NSBundle.mainBundle().pathForResource("Sequence List", ofType: "plist")
But I can't see that there is an equivalent to objectAtIndex in swift. If I try the following I get an error message advising "string does not have a member named subscript", which apparently means I need to unwrap path.
let firstQuestion = path[0]
The methods you are calling, like NSBundle.mainBundle().pathForResource, return optionals, because they might fail. In Objective-C, that failure is indicated by a nil, whereas Swift uses optionals.
So in your example:
var path = NSBundle.mainBundle().pathForResource("Sequence List", ofType: "plist")
path is of type Optional<String> (or String?) and not of type String. Optional<String> doesn’t have a subscript method (i.e. doesn’t support [ ]). To use the string within, you have to check if the optional contains a value (i.e. the call to pathForResource was successful):
// the if let syntax checks if the optional contains a valid
if let path = NSBundle.mainBundle().pathForResource("Sequence List", ofType: "plist”) {
// use path, which will now be of type String
}
else {
// deal with pathForResource failing
}
You can read more about optionals in introduction of the Swift book.
You haven't translated the entire first line from Objective-C. You are missing the call to NSMutableArray which creates the array from the contents of the file. The original code is confusing because it calls the contents of the file path when it is really questions. Try this:
if let path = NSBundle.mainBundle().pathForResource("Sequence List", ofType: "plist") {
let questions = NSMutableArray(contentsOfFile: path)
//Shuffle the array of questions
numberSequenceList = self.shuffleArray(questions)
currentQuestion = currentQuestion + 1
if Round == 1 {
//Take first object in shuffled array as the first question
let firstQuestion = numberSequenceList[0] as NSArray
//Find question and populate text view
let string = firstQuestion[0] as NSString
self.lblNumber.text = string
//Find and store the answer
let findAnswer = firstQuestion[1] as NSString
Answer = findAnswer.intValue
}
}