I'm attempting to customize the "Page-Based Application" template and am new to Swift (and all programming for that matter). In my ModelController.swift I've created a 2D array to use as the primary datasource.
However, when I go to print it or use it as label.text it is including the parenthesis. How do I get just the strings of the array element I'm calling for?
class ModelController: NSObject, UIPageViewControllerDataSource {
var pageData: NSArray = [[String]]()
override init() {
super.init()
// Create the data model.
pageData = [["right", "wrong"], ["left", "right"]]
println(pageData[1]) //For
}
I'm not sure what else from this file is relevant but here's another bit.
func indexOfViewController(viewController: DataViewController) -> Int {
// Return the index of the given data view controller.
// For simplicity, this implementation uses a static array of model objects and the view controller stores the model object; you can therefore use the model object to identify the index.
if let dataObject: AnyObject = viewController.dataObject {
return self.pageData.indexOfObject(dataObject)
} else {
return NSNotFound
}
}
If you just want left, right
println("\(pageData[1][0]), \(pageData[1][1])")
If you literally want "left, right" with the double quotes included it's
println("\"\(pageData[1][0]), \(pageData[1][1])\"")
The reason you got the results you did is because pageData[1] isn't a String it's an NSArray. Because NSArray conforms to the Printable protocol it supplies its .description when used in println.
Related
I have a protocol, structure and instances + protocol and class, which should change the instances of the structure:
protocol Cars {
var car: String { get set }
var accesories: [String] { get set }
}
struct Car: Cars {
var car: String
var accesories: [String]
}
var carOne = Car(car: "Car One", accesories: ["accessoryOne", "accessoryTwo"])
var carTwo = Car(car: "Car Two", accesories: ["accessoryTwo"])
protocol Dealership {
static var cars: [Car] { get set }
static func addAccesories(add: [String])
}
Next, in DealershipOne I want to make a func addAccesories that will add an array of strings to the instance property, I try do it this way and some other ways, but I get the error Cannot use mutating member on immutable value: '$0' is a 'let' constant.
class DealershipOne: Dealership {
static var cars = [carOne, carTwo]
static func addAccesories(add: [String]) {
cars.forEach{ $0.accesories.append(contentsOf: add) } // ERROR
}
}
How do I resolve this?
You can not add new value to direct iterated objects. $0 is a let object so you can not add value.
A solution to use index and update the value.
cars.indices.forEach{ cars[$0].accesories.append(contentsOf: add) }```
The anonymous closure parameter ($0) within your forEach loop is an immutable copy of a car from the cars you’re trying to iterate.
You could make a mutable copy with var car = $0, but even that won’t help, because you would be mutating the copy, and not the actual cars in the static cars array.
If you would like to keep using struct to model your cars, then the only way to modify properties of those cars within the array is to get an l-value (in c++ lingo) via assignment through a subscript:
func add(accessories: [String]) {
for i in cars.insides {
cars[i].accessories += accessories
}
}
(I also made some minor tweaks to make this more idiomatic, such as t changing the function name, and using the += operator for arrays instead of Array.append(contentsOf:))
I am keeping track of a number of delegate objects in an array delegates
To qualify as a valid delegate the objects need to conform to the BSBSystemDelegate protocol.
So here is the array declaration:
private var delegates: [BSBSystemDelegate] = []
When an object registers with the BSBSystem, it is appended to the array:
public func registerDelegateWith(_ viewController: BSBSystemDelegate)
{
self.delegates.append(viewController)
}
That's working fine.
The problem I'm running into with swift and it's awful and confusing syntax is when I need to 'deregister' a delegate i.e. remove it from the array, if it exists.
Here's what I've tried:
public function deregisterDelegate(_ viewController: BSBSystemDelegate)
{
for delegate in self.delegates
{
if delegate === viewController
{
self.delegates.removeAll(where: viewController)
}
}
}
That doesn't work.
I just want to remove the object in the array when it's the same object I'm asking to remove.
I've been fighting swift for over an hour. Can someone please explain where I'm going wrong?
Here's is Apple's example:
And here is my code and the crazy dumb error it keeps giving me:
self.delegates.removeAll(where: { $0 === viewController }) will work but your protocol needs to be declared as class-bound in order to use the === operator which only works with reference types.
You would have to declare your protocol as:
protocol BSBSystemDelegate: AnyObject {
...
}
The error message isn't useful because the compiler is confused but if you break out your closure declaration on to a separate line:
let shouldBeRemoved: (BSBSystemDelegate) -> Bool = { $0 === viewController }
self.delegates.removeAll(where: shouldBeRemoved)
You get a more useful binary operator '===' cannot be applied to two 'BSBSystemDelegate' operands message.
Assuming you change your protocol to make it class-bound, as described by Dan, you could also use code like this:
if let index = array.firstIndex(where: { $0 === aFoo }) {
array.remove(at: index)
}
That would probably be faster for a large array, since it would stop on the first occurence of a match. (removeAll(where:) will always check every element in the array for a match.)
However, the code above would only remove the first instance of the object from the array if the exact same object has been added more than once.
I'm passing an array of a specific model by reference between ViewControllers.
If I change any value of a specific element in the array it reflects well in all ViewControllers but when I remove an element from that array it doesn't reflect to the other controllers.
Does the remove(at: ) function create new array and refer to another address?
And if so how to delete an element without changing the address of array so it can reflect this change on the other view controllers?
Swift Arrays are value types (specifically, an array is a struct), not reference types, so you are mistaken when you say that you are "passing an array of a specific model by reference between view controllers". You can only ever pass a Swift array as a value.
Arrays, like other structs, have copy-on-modify semantics. As soon as you change the array itself a copy is made and the change is made to the copy.
Now, in your case the array contains references to model objects; When you update the model object you change the object itself, not the reference held in the array, so you see the change reflected in all of your view controllers.
An analogy might be the difference between adding a house to a street (which changes the street itself) versus changing the occupants of an existing house on the street.
I would suggest you implement a model object that provides abstraction from the underlying array so that you have better code and avoid the issue with array references.
One approach could be something like:
struct MyModel {
let name: String
let size: Int
}
class MyData {
private var _models = [MyModel]()
var models: [MyModel] {
return _models
}
func insert(model: MyModel) {
self._models.append(model)
}
func removeModel(at: Int) {
guard at >= 0 && at < _models.count else {
return
}
self._models.remove(at: at)
}
}
Although this isn't ideal as it still requires model consumers to know indices in the underlying array. I would prefer something like this:
struct MyModel: Hashable {
let name: String
let size: Int
}
class MyData {
private var _models = [MyModel]()
var models: [MyModel] {
return _models
}
func insert(model: MyModel) {
self._models.append(model)
}
func remove(model: MyModel) -> Bool {
if let index = self._models.index(of: model) {
_models.remove(at: index)
return true
} else {
return false
}
}
}
Now I don't need to know what internal collection MyData uses to store the models.
If you need to pass an array (or any other value type) by reference, you could go through an intermediate structure that manages the indirection for you.
[EDIT] changed to use KeyPaths available in Swift 4.
// Generic class to hold a "weak" reference to a property from an object
// including properties that are valued types such as arrays, structs, etc.
// This is merely an encapsulation of Swift's native KeyPath feature
// to make the code a bit more readable and simpler to use
//
class ReferenceTo<ValueType> { var value:ValueType! { get { return nil} set {} } }
class Reference<OwnerType:AnyObject,ValueType>:ReferenceTo<ValueType>
{
internal weak var owner:OwnerType!
internal var property:ReferenceWritableKeyPath<OwnerType,ValueType>! = nil
internal var valueRef:KeyPath<OwnerType,ValueType>! = nil
init(_ owner:OwnerType, _ property:ReferenceWritableKeyPath<OwnerType,ValueType>)
{ (self.owner,self.property) = (owner,property) }
init(_ owner:OwnerType, get valueRef:KeyPath<OwnerType,ValueType>)
{ (self.owner,self.valueRef) = (owner,valueRef) }
override var value:ValueType!
{
get { return valueRef != nil ? owner?[keyPath:valueRef] : owner?[keyPath:property] }
set { owner?[keyPath:property] = newValue }
}
}
With this generic class you can create references to valued type properties of object instances and manipulate them anywhere in your code as if the valued type property was a reference type.
// Example class with a read/write and a read-only property:
class MyObject
{
var myArray = [1,2,3,4]
var total:Int { return myArray.reduce(0,+) }
}
var instance:MyObject! = MyObject()
// create a reference to the array (valued type)
// that can be used anywhere and passed around as a parameter
let arrayRef = Reference(instance, \.myArray)
// the value is accessed and manipulated using the
// "value" property of the reference
arrayRef.value.remove(at:2)
arrayRef.value.append(5)
print(instance.myArray) // [1,2,4,5]
// Read-only properties can also be manipulated as
// references
let valueRef = Reference(instance, get:\.total)
print(valueRef.value) // 12
The Reference class allows passing the value as a reference to function parameters
// a function that expects a reference to an array
// would be declared as follows
func changeArray(_ array:ReferenceTo<[Int]>)
{ array.value.insert(9, at: 1) }
// the reference can also be used as an inout parameter
func shift(_ array:inout [Int])
{ array = Array(array.dropFirst()) + Array(array.prefix(1)) }
changeArray(arrayRef)
shift(&arrayRef.value!)
print(instance.myArray) // [9,2,4,5,1]
...
// the reference uses a weak link to the owner
// of the referenced property or value
// so there will be no strong reference cycle issues even
// if the reference is used in an object held strongly
// by the owner itself
instance = nil
print(arrayRef.value) // none ... no more value after the owner is gone
I am learning how to build apps and working with Swift for this project.
I had a buddy help me pull data in from a website and it looks like he created classes with variables and mapped them to certain extensions (IE "Username") so when I call the variable data such as profile I would call it. The below uses luck_30 able to store "Stats.luck_30"
luck_30.text = profile.luck_30
So inside one of my variables that is in this "Profile" class is setup into an array. I can pull the array out of the class, but I can't seem to do for while statement replacing the [#] with a variable from the for command.
func aliveWorkers(profile: Profile) -> NSNumber{
var myworkers : Array = profile.workers!
//this test works and returns the proper value
var testworker: NSNumber = myworkers[0].alive!
println("The satus of the test worker is " + testworker.description)
/* This code is giving error "Could not find member alive" it does not ifor var
for ifor in myworkers{
var thisworker: NSNumber = myworkers[ifor].alive! as NSNumber
}
*/
return 42
}
Your variable ifor is not a counter, it is an actual object. You could do something like this:
for worker in myWorkers {
let workerIsAlive = worker.alive!
}
Alternatively, if you need the index,
for i in 0 ..< myWorkers.count {
let worker = myWorkers[i]
let workerIsAlive = worker.alive!
}
If you need both:
for (i, worker) in enumerate(myWorkers) {
let workerIsAlive = worker.alive!
}
And as a matter of style, I would stay away from NSNumber and use Int or Bool or whatever the data actually is. Also, it looks like the alive variable should not be optional, as you're unwrapping it everywhere. To avoid "mysterious" crashes later, you may want to think about making it a non-optional type.
when using a for in loop, your loop variable isn't an index, its the objects you're looping through. so..
func aliveWorkers() {
var myworkers = [1, 2, 3]
//this test works and returns the proper value
let testworker = myworkers[0]
print("The satus of the test worker is \(testworker)")
for ifor in myworkers {
print(ifor)
}
}
Notice a few things... you don't need to use + to concatenate those strings. you can just use string interpolation. \(variable) inserts the value of variable in the string.
Try to use let instead of var when you don't change the variable. You don't need to explicitly define type on variables either.
i use the following function to retrieve a random person from an array:
func getRandomPerson() -> String{
if(personArray.isEmpty){
return ""
} else {
var tempArray: [String] = []
for person in personArray{
tempArray += [person.getName()]
}
var unsignedArrayCount = UInt32(tempArray.count)
var unsignedRandomNumber = arc4random_uniform(unsignedArrayCount)
var randomNumber = Int(unsignedRandomNumber)
if tempArray.isEmpty {
return ""
} else {
return tempArray[randomNumber]
}
}
}
I would like to use this function inside an array of strings, Like this:
var theDares: [String] = ["Dare1 \(getRandomPerson())", "Dare2", "Dare3", "Dare4", "Dare5"]
But when i use the functions, it only runs the function once. Can you make the function run everytime you use the "Dare1" in this instance.
Thanks in advance
I think you are asking if you can set up your array so every time you fetch the object at index 0, it re-builds the value there.
The short answer is no. Your code is creating an array of strings, and the item at index 0 is built ONCE using a function call.
However, it is possible to make a custom class implement the subscript operator. You could create a custom object that looks like an array and allows you to index into it using an Int index. In response to the index operator you could run custom code that built and returned a random string.
Since it sounds like you're a beginning programmer creating a custom class the implements the subscript operator might be beyond your current abilities however.
Try like this:
let personArray = ["John", "Steve", "Tim"]
var randomPerson: String {
return personArray.isEmpty ? "" : personArray[Int(arc4random_uniform(UInt32(personArray.count)))]
}
println(randomPerson) // "Steve"