I have this code, but it is only appending the first uiview. I also want the label in the uiview to display the string from arrayTwo. It's only printing the count right now.
I have the stackView in storyboard set to fill equally. And only top, trailing and leading constraints.
If I don't care about animation, can I append to a container UIView and the same code should just work? Obviously swapping out the stackView for a uiview
class ViewController: UIViewController {
#IBOutlet weak var arrayButton: UIButton!
#IBOutlet weak var stackView: UIStackView!
var arrayView = MyView() /// lives inside a nib only has one uilabel
var arrayOfView: [MyView] = []
var arrayTwo = ["one", "two", "three", "four", "five"]
var arrayIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
stackView.spacing = 4
}
func appendViews() {
for i in 0...arrayTwo.count - 1 {
arrayOfView.append(arrayView)
//arrayOfView[I].myLabel.text = arrayTwo[i]
arrayOfView[i].myLabel.text = arrayTwo.first
stackView.addSubview(arrayOfView[i])
stackView.addArrangedSubview(arrayOfView[i])
}
}
#IBAction func arrayButton Action(_ sender: Any) {
appendViews()
}
}
This line of code repeatedly adds the same object to the array:
arrayOfView.append(arrayView)
I assume that MyView is a class inherited from UIView, so in Swift in has reference type semantics (read more about value and reference types). When this line gets executed, no new instance of MyView gets created, instead the reference to the same existing object gets added to the array. Later, when you add subviews to the stack view, it just tries adding the same view over and over again.
What you need instead is to create a new instance of MyView for every iteration. That said, you'll no longer need the arrayView property.
There are also a few other issues that can be addressed:
The raw for-loop can be replaced with a much safer for-in statement to avoid operating directly with array indices.
You almost never call the addSubview for UIStackView because it will not automatically arrange the views added that way. Calling just addArrangedSubview is what you always want.
The arrayIndex property seems to be unused and can be deleted.
arrayTwo is not a good name because it doesn't tell anything about the contents and the semantics, try making it more descriptive like labelTexts.
After all you get something like this:
for text in labelTexts {
let myView = MyView()
arrayOfView.append(myView)
myView.myLabel.text = text
stackView.addArrangedSubview(myView)
}
Related
Hi I'm trying to shuffle an array which I can then use in all my IBAction functions thereafter in the order that I shuffled them. Example of code which is the same idea as what I have...
Class ViewContoller: UIViewContoller
Let numbers = [1,2,3,4,5]
override func viewDidLoad() {
super.viewDidLoad() }
I've tried to write a function here to create an array which has been shuffled so I can use that shuffled array ...
func shuffledArray() -> Array<Int> {
let numbersArray = numbers.shuffled()
return numbersArray }
let myListOfShuffledNumbers = shuffledArray()
Now this works in playground.. but when I try this my viewcontroller.swift I get error 'cannot use instance member 'shuffledArray' within property initializer; property initializers run before self is available.
So I know if I'm in a IBAction func I can run the function let shuffledNumbers = shuffledArray()and get an Array of shuffled numbers to use but my problem is I need to use that same shuffle order in another IBAction function. So how can I make a universal array of shuffled numbers to use anywhere on my view controller.
I've tried lazy var myListOfShuffledNumbers = shuffledArray()
This gets rid of my error but it doesn't help me because I get a 2 different list order when I use 'myListOfShuffledNumbers' in 2 different IBAction functions
What can I do ?? Thanks
You can define a property that you can access from anywhere in your ViewContoller, like this:
class ViewContoller: UIViewContoller {
let unshuffledNumbers = [1,2,3,4,5]
var myListOfShuffledNumbers = [Int]() /// here! it's still empty at first
#IBAction func firstButtonPressed(_ sender: Any) {
myListOfShuffledNumbers = unshuffledNumbers.shuffled()
}
#IBAction func secondButtonPressed(_ sender: Any) {
/// do something with myListOfShuffledNumbers here...
}
}
At first, myListOfShuffledNumbers is still an empty array of Ints. But inside your first IBAction, you can assign that to unshuffledNumbers.shuffled(). Then, you can access it from inside your second IBAction.
Also it might be easier to just write [Int] instead of Array<Int> -- they are the same thing.
func shuffledArray() -> [Int] {
let numbersArray = numbers.shuffled()
return numbersArray
}
If you want an array that is shuffled then just convert it to a set. Sets will not keep the original order of the array.
I want a picture to move to the bottom. If I press a button the pic should move down by 1.
I added the picture and a button:
var corX = 0
var corY = 0
var runter: UIButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
var image = UIImage(named: "panzerBlau.jpg");
var panzer = UIImageView(frame: CGRectMake(corX, corY, 30, 40)); //
override func viewDidLoad() {
super.viewDidLoad()
panzer.image = image; //
self.view.addSubview(panzer); //
runter.frame = CGRectMake(100, 30, 10 , 10)
runter.backgroundColor = UIColor.redColor()
view.addSubview(runter)
runter.addTarget(self, action: "fahren", forControlEvents:UIControlEvents.TouchUpInside)
}
At least I said in function "fahren" to move the picture down by 1.
func fahren(){
corY += 1
panzer.frame = CGRectMake(corX, corY, 30, 40) //
self.view.addSubview(panzer);
}
So my problem is: I get several errors with these corX and corY thing. Without them it works perfectly but than its like a single-use button. The errors are: ViewController.Type does not have a member named corX and ViewController.Type does not have a member names panzer Where I get the errors I made // to show in which lines.
PS: I use Xcode Beta5
Here's the complete code without anything else:
import UIKit
class ViewController: UIViewController {
var corX = 0
var corY = 0
var runter: UIButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
var image = UIImage(named: "panzerBlau.jpg");
var panzer = UIImageView(frame: CGRectMake(corX, corY, 30, 40));
override func viewDidLoad() {
super.viewDidLoad()
panzer.image = image;
self.view.addSubview(panzer);
runter.frame = CGRectMake(100, 30, 10 , 10)
runter.backgroundColor = UIColor.redColor()
view.addSubview(runter)
runter.addTarget(self, action: "fahren", forControlEvents:UIControlEvents.TouchUpInside)
}
func fahren(){
corY += 100
panzer.frame = CGRectMake(corX, corY, 30, 40)
self.view.addSubview(panzer);
}
}
#MartinR has pointed out the major issue here:
var corX = 0
var corY = 0
var panzer = UIImageView(frame: CGRectMake(corX, corY, 30, 40))
The problem is that a Swift default initializer cannot refer to the value of another property, because at the time of initialization, the property doesn't exist yet (because the instance itself doesn't exist yet). Basically, in panzer's default initializer you are implicitly referring to self.corX and self.corY - but there is no self because self is exactly what we are in the middle of creating.
One workaround is to make the initializer lazy:
class ViewController: UIViewController {
var corX : CGFloat = 0
var corY : CGFloat = 0
lazy var panzer : UIImageView = UIImageView(frame: CGRectMake(self.corX, self.corY, 30, 40))
// ...
}
That's legal because panzer doesn't get initialized until later, when it is first referred to by your actual code. By that time, self and its properties exist.
Your dependent property needs to be:
lazy
Have an explicit : Type
Use self. to access other properties
Example:
let original = "foo"
// Good:
lazy var depend: String = self.original
// Error:
var noLazy: String = self.original // Error: Value of type '(NSObject) -> () -> URLData' has no member 'original'
lazy var noType = self.original // Error: Value of type '(NSObject) -> () -> URLData' has no member 'original'
lazy var noSelf: String = original // Error: Instance member 'original' cannot be used on type 'YourClass'
I'm addressing the title of the question:
Both lazy and computed properties help you deal with when the initial value for a property is not known until after the object is initialized. But there are some differences. I've highlighted the differences with bold.
If you simply need to initialize a variable after some other variable(s) is initialized then you should use lazy ie if the point is to simply add a delay (so all required properties get initialized before) then using lazy is the right way to go for it.
But if you need to constantly change a variable based on another, then you need a computed property that would work both ways:
if the computed property set then it sets the variables its related stored properties
if the stored properties are set (or are reset again) then it will trigger a change in then computed property.
if you change the lazy property's value it won't affect the storied properties that it was based on. see here
A good example for using a lazy property would be that once you have firstName & lastName then you would lazily instantiate a fullName and likely you would never change the firstName lastName of your object your fullName is a onetime only...
Or perhaps something that can only be done by lazy properties is that up until you don't access the property it won't ever get initialized, therefore this would decrease the initialization load of your class. Loading a heavy calculation.
Additionally using the lazy will signal to other developers: "Hey first go read about the other properties and understand what they are...then come to this lazy property...since the value of this is based on them + this is likely a heavy computation that shouldn't be accessed too early..."
As for computed property a good example would be if you set the temperature to Fahrenheit then you also want your celsius temperature to change its value...and if you set the celsius temperature then again you want to change your Fahrenheit value.
As a result computed property would add extra computation...and if your computation is very simple and isn't called too frequently then it's nothing to worry about but if it get's called too often or is very CPU-consuming then it might be better to think of other options...
//
// ViewController.swift
//
// Created by Shivank Agarwal on 19/05/18.
// Copyright © 2018 Shivank Agarwal. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
var corX = 0
var corY = 0
var runter: UIButton = UIButton()
var image = UIImage(named: "panzerBlau.jpg")
var panzer = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
panzer.image = image;
self.view.addSubview(panzer);
panzer.frame = CGRect(x: CGFloat(corX), y: CGFloat(corY), width: 30, height: 40)
runter.backgroundColor = UIColor.red
view.addSubview(runter)
view.addSubview(panzer)
runter.addTarget(self, action: Selector(("fahren")), for:UIControlEvents.touchUpInside)
}
private func fahren(){
corY += 100
}
private func updatePanzerFrame(){
panzer.frame = CGRect(x: CGFloat(corX), y: CGFloat(corY), width: 30, height: 40)
}
}
Note: Do not add panzer imageView every time when user tap only add it on viewDidLoad()
I know this is going to be super elementary, but I have this piece of code:
var labels: [String]?
func initVC(image: Images){
self.image = image
let tempLabels = image.label?.allObjects as! [Labels]
for i in 0..<tempLabels.count{
labels?.append(tempLabels[i].label!)
}
}
labels is in the public scope, so the function should have access to it, but when the loop runs through, labels is still nil with no elements.
When I po during debugging, tempLabels is as I expect it to be with 2 string elements.
I'm pretty sure this is a very simple problem, but I guess I'm just out of it right now.
Labels has never been initialised. Change
var labels:[String]?
to
var labels:[String] = []
You are declaring the labels variable but never allowing it to store information. This means that it does not necessarily exist, since it is not initialized, and therefore cannot be used.
For it to be useable, you must initialize it
var labels:[String] = []
Yep, it was super simple.
Changed
var labels: [String]?
To
var labels = [String]()
I want to pass an array of strings through segue, but my app crashes. Here is what I have in the starting ViewController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "sendToOther") {
let svc = segue.destinationViewController as! OtherMinerals;
svc.toPass1 = String(DataSet[0])
svc.toPass2 = String(DataSet[1])
and this is what I have in the receiving ViewController
var toPass:String!
var toPass2:String!
so, I am passing every item separately through segue, but that's not elegant. I'd like to pass the whole array through, but for some reason I can't get the code right. Any genius out there to assist??
Just create a [DataSet] variable in your second ViewController and pass your whole DataSet array to your second ViewController instead of the two strings:
//second viewcontroller:
var dataSetArray:[DataSet]
//first viewcontroller:
svc.dataSetArray = yourDataSet
Change the type of the variable "toPass" to be [String]. If your DataSet is of the same type you can pass the array like so
svc.toPass = DataSet
environment XCode 6.4 Swift 2.something
I have two simple Custom Classes
class Stock {
var ageArray = [3,6,9,12,15,18,24,30,36,42,48,54,60,66,72] // in months
var beastArray = ["Angus","Braford","Charolais","Droughtmaster","Euro","Hereford"]
var breedArray = ["Calves","Vealers","Weaners","Heifers","Steers","Cows","Bulls"]
var priceArray = [600.00,650.00,700.00,750.00,800.00,850.00,900.00,950.00,1000.00,]
}
and
class Sale {
var age : Int = 0
var beast : String = " "
var breed : String = " "
var price : Double = 0.00
}
Now to my view controller
I've instantiated a report from Sale Class. The ViewController holds an array of all those reports as I'm taught in nuremous places, declaring it first and instatiating it in viewDidLoad
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var picker: UIPickerView!
var report = Sale()
var sheets = Stock()
var saleArray : [Sale] = [] // declaration
override func viewDidLoad() {
super.viewDidLoad()
saleArray = [Sale]() // instantiating
}
the other delegate functions of the picker work sweetly, no need to show them all here, but this is where the two class Objects meet to pass values
func pickerView(picker: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
switch(component) {
case 0:
report.breed = sheets.breedArray[row]
break
case 1:
report.beast = sheets.beastArray[row]
break
case 2:
report.age = sheets.ageArray[row]
break
default:
report.price = sheets.priceArray[row]
break
}
}
and an onscreeen button loads them into an array
#IBAction func AcceptValues(sender:UIButton) {
saleArray.append(report)
}
that's all there is to it.
This is the result as seen in the debugger with a breakpoint set as below after 3 times around with [0] supposed to have Angus Calves and [1] having Droughtmaster Heifers
so the question is why would the saleArray be filling up with n instances of whatever was last entered? I know i'm not looking at the last entry n times. But curiously the address for report is all the same at 7A19C000.
I thought it might be the compiler can't infer the array type after the instatiaton in viewDidLoad. Examining various tutorials and the code supplied with them treats the matter more or less as I have above. Which is the reason for the title, I'm sure Swift can hold an array of objects other than the usuals.
I've even gone to the expense at purchasing not one, but two of Matt Neuberg's books just to check on how to set up an Object Array, but like all the material available they just discuss arrays of Int and Double and String and no more. Sometimes the most infuriating bugs are the most staring-at-you-in-your-face, and I've been chasing this for close on a week.
There's little else to it the code.
EDIT:
thanks to the two contributors below, the comments show what i did to make it work. But I have a feeling its not elegant:
I also made Stock a struct.
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var picker: UIPickerView!
#IBOutlet weak var textPanel: UITextView!
var sheets = Stock()
var saleArray : [Sale] = []
var localBreed = " " // introduced some handover values
var localBeast = " " // because report is out of scope
var localAge = 0 // in the picker functions
var localPrice = 0.00
.
.
.
#IBAction func AcceptValues(sender:UIButton) {
var report = Sale() // new reports instantiated here
report.breed = localBreed // and locals handover values to
report.beast = localBeast // report instance before adding
report.age = localAge // to array
report.price = localPrice
saleArray.append(report)
}
.
.
.
func pickerView(picker: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
switch(component) {
case 0:
localBreed = sheets.breedArray[row]
break
case 1:
localBeast = sheets.beastArray[row]
break
case 2:
localAge = sheets.ageArray[row]
break
default:
localPrice = sheets.priceArray[row]
break
}
}
I believe that it is because report is a single instance of Sale. Therefore each time you select a new combination - you are not creating a new Sale item, rather you are changing the values associated with that single instance of Sale called report. When you press the button it adds another reference to that single report instance.
These class objects are passed by reference - i.e. when you add report to the saleArray it is not creating a copy of the values - rather you are passing a reference to the actual instance. Each time you press the button you are adding a reference to the same instance of Sale. And the values associated with that instance are then getting changed when you use the picker. It's the same instance over and over.
You would need to change your code so that you create a new instance of Sale for each selection. As a quick hack, to test this, you could add code to your button which creates a new Sale instance based on the selected values - and then adds that to saleArray.
ETA - one other thing I noticed: You are instantiating saleArray twice - both at //declaration and in viewDidLoad(). Although that is not causing the issue you have described, it could cause an issue under potential circumstances.