Swift 4 : Adding UIView into the array programmatically with for loop - ios

I'm using scrollView paging and I want to add UIViews into the scrollView programmatically with for loop. But I guess I'm missing something.
Here is my code :
func createSlide() ->[Slide]{
for i in 0..<datas.count{
slideAll?.append(Bundle.main.loadNibNamed("ViewTest", owner: self, options: nil)?.first as! Slide)
slideAll![i].testLabel.text = datas[i]
}
return slideAll!
}
Is this possible that I can add UIView like that? If I can, how can I fix this code piece?
Edit :
I realized that I initialize the slideAll with no value on the top like that :
var slideAll:[Slide]?
How can I initialize this correctly? Should I initialize this on the function?

for i in 0...datas.count will loop to the last slot array+1, then it will yell error because at that index there's no value, you should use for i in 0..<datas.count or for slide in datas instead

Instead of this line:
var slideAll:[Slide]?
say this:
var slideAll = [Slide]()
Then remove the Optional unwrap notations from your references to slideAll in the rest of the code.

Make sure datas is initialized.
use for-loop as below:
for text in datas {
// DO SOMETHING
}
And I hope, you are subview-ing the views on uiscrollview somewhere in the code and it is not mentioned in the above question?

Related

Calling an ImageView variable inside an if statement

I'm using Swift in Xcode and that's my issue:
I want to declare a variable where I want to store an UIImageView (Not an UIImage, but an UIImageView), so I tried something like that
var nameVariable = UIImageView()
Then I need to recall this nameVariable inside an if statement
if(\(nameVariable).center.y > something){..}
I tried using "(nameVariable)" but it doesn't work, can you help me?
Declare Variable
var img=UIImageView()
Then use it
if img.center.y > 50 {
//do stuff here
}

Pass a Class (UILabel) with its properties through a function Swift

I am wondering if anyone knows how to (if possible) pass a UILabel through a function while being able to access and change its properties? Here's what I have:
func plusMinusChange(minus: UILabel, plus: UILabel) {
if (minus.hidden) {
minus.hidden=false
plus.hidden=true
} else {
minus.hidden=true
plus.hidden=false
}
}
And here's how I am calling it:
plusMinusChange(firstMinus, firstPlus)
I know this is probably really illogical but I want to give it a try anyways. If you were wondering, firstMinus and firstPlus are linked to UILabels on the storyboard.
Calls to methods (that is, funcs defined within a class or other type) require parameter labels for the second (and subsequent) parameter but not the first. If you want to change which labels are required at the call site, you change the declaration.
To require a label on the first parameter:
func plusMinusChange(#minus: UILabel, plus: UILabel) {
To require no label on the second:
func plusMinusChange(minus: UILabel, _ plus: UILabel) {
There is no need to use a conditional if there. You can use ! in front of the property to toggle its value as follow:
minus.hidden = !minus.hidden
plus.hidden = !plus.hidden

Can a Swift initializer have a variadic parameter?

Can a Swift init have a variadic parameter at the end so you can send multiple values of that type to the init?
An example would be to create a class that has an array of UIViews. Would the following work? Is it considered "legit" to do this? (I know I could just pass an array of views, just wondering if this is an option.)
class viewsContainer {
var myViews: [UIView] = []
init(views: UIView...) {
for view in views {
myViews.append(view)
}
}
}
Yes, it's a valid approach, but you should set different frames to not overlap the subviews.
Perfectly legal. I just think it's best to pass an array of views instead (so myViews can be a constant), though. You could create a temporary array with the variadic parameter, and assign it to a private constant, but you, know...

Create a copy of a UIView in Swift

Because objects are reference types, not value types, if you set a UIView equal to another UIView, the views are the same object. If you modify one you'll modifying the other as well.
I have an interesting situation where I would like to add a UIView as a subview in another view, then I make some modifications, and those modifications should not affect the original UIView. How can I make a copy of the UIView so I can ensure I add that copy as a subview instead of a reference to the original UIView?
Note that I can't recreate the view in the same way the original was created, I need some way to create a copy given any UIView object.
You can make an UIView extension. In example snippet below, function copyView returns an AnyObject so you could copy any subclass of an UIView, ie UIImageView. If you want to copy only UIView you can change the return type to UIView.
//MARK: - UIView Extensions
extension UIView
{
func copyView<T: UIView>() -> T {
return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self)) as! T
}
}
Example usage:
let sourceView = UIView()
let copiedView: UIView = sourceView.copyView()
You can't arbitrarily copy an object. Only objects that implement the NSCopying protocol can be copied.
However, there is a workaround: Since UIViews can be serialized to disk (e.g. to load from a XIB), you could use NSKeyedArchiver and NSKeyedUnarchiver to create a serialized NSData describing your view, then de-serialize that again to get an independent but identical object.
Update for iOS >= 12.0
Methods archivedData(withRootObject:) and unarchivedObject(with:) are deprecated as of iOS 12.0.
Here is an update to #Ivan Porcolab's answer using the newer API (since 11.0), also made more general to support other types.
extension NSObject {
func copyObject<T:NSObject>() throws -> T? {
let data = try NSKeyedArchiver.archivedData(withRootObject:self, requiringSecureCoding:false)
return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? T
}
}
This answer shows how to do what #uliwitness suggested. That is, get an identical object by archiving it and then unarchiving it. (It is also basically what Ivan Porkolab did in his answer, but in a more readable format, I think.)
let myView = UIView()
// create an NSData object from myView
let archive = NSKeyedArchiver.archivedData(withRootObject: myView)
// create a clone by unarchiving the NSData
let myViewCopy = NSKeyedUnarchiver.unarchiveObject(with: archive) as! UIView
Notes
Unarchiving the data creates an object of type AnyObject. We used as! UIView to type cast it back to a UIView since we know that's what it is. If our view were a UITextView then we could type cast it as! UITextView.
The myViewCopy no longer has a parent view.
Some people mention some problems when working with UIImage. However, see this and this answer.
Updated to Swift 3.0
I think that you should link you UIView with a .nib and just create a new one.
Property will not be the same, but you keep appearance and methods.
An addition solution could be to just create a new UIView and then copy over any critical properties. This may not work in the OP's case, but it could very well work for other cases.
For example, with a UITextView, probably all you would need is the frame and attributed text:
let textViewCopy = UITextView(frame: textView.frame)
textViewCopy.attributedText = textView.attributedText
This is mostly just an update for iOS 12+
extension NSObject {
func copyObject<T:NSObject>() throws -> T? {
let data = try NSKeyedArchiver.archivedData(withRootObject:self, requiringSecureCoding:false)
return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? T
}
Additionally you can use this pattern to copy View controller view
let vc = UIViewController()
let anotherVc = UIViewController()
vc.view = anotherVc.copyView()
You may need this for caching view controller or cloning.

Weird error in accessing the text of UIButton in swift

When I write a simple function such as this:
#IBAction func buttonTapped(theButton:UIButton) {
println(theButton.titleLabel.text);
}
It gives me an error: UILabel doesn't have a label called text.
However, when I change it to this:
#IBAction func buttonTapped(theButton:UIButton) {
println(theButton.titleLabel?.text);
}
It works fine, but it prints out something like this:
Optional("1");
What I am doing wrong? I am expecting a value of 1. But it is printing out Optional("1") and secondly, it is working fine when println(theButton.titleLabel?.text);
You can get directly from
let title = theButton.currentTitle!
Optional chaining makes the result optional, so you are printing optional value: https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/OptionalChaining.html
With optional binding you can print the value only if it exits.
if let text = theButton.titleLabel?.text {
println(text)
} else {
// text doesn't have value
}
#Kirsteins's answer shows how to obtain the button label text in a safe manner.
Remember that:
UIButton has a titleLabel, which is an optional UILabel.
UILabel has a text property, which is an optional String
so there are 2 optionals in the chain. You can use optional binding as in #Kirsteins's answer, or use forced unwrapping:
let text = theButton.titleLabel!.text!
which however I discourage using, because if any of the 2 is nil you'll have a runtime exception. But for completeness it's worth mentioning.
The buttons titleLabel property returns an optional UILabel, that means it's possible that the button doesn't have a titleLabel.
var titleLabel: UILabel? { get }
If you don't set a title to the button, then the button doesn't have a titleLabel property, the iOS framework adds the titleLabel only if the button has a title, I think this happens to reduce memory.
This is why you have to put the "?" (is called optional chaining you can read about it here http://bit.ly/1vrSOi1) in that line, but this usually get auto completed by Xcode itself.
Kirsteins answers it correctly but misses one small detail
if your object can be nil (optional) you need to check first if it exists to then access its value, like this:
if let text = theButton.titleLabel?.text {
println(text)
}
but you can also ignore the if and just call it like this:
let text : String = theButton.titleLabel?.text
// If theButton.titleLabel don't exists then text will be nil
this happen if the IBOutlet was declared with ? but if you declare with ! that means you know that it could be nil, but you never want it to be nil, for a IBOutlet i prefer this approach since if the IBOutlet is not connected then maybe something is worn with my code.
#IBOutlet var theButton : UIButton!
// and get text value as
theButton.titleLabel!.text
this will ensure theButton.titleLabel could be nil, but in this part of code it is required, hope this helps to understand the difference between optional (?) and optional required (!)

Resources