Best way to init an array of views in swift? - ios

I'm writing a new subclass of UIView using swift. I want to have an array of views, and I want to instantiate them in the initial declaration, for clarity.
If this was an Int array, I could do something like this:
let values: [Int] = (0...4).map() { $0 }
and so I'm trying to come up with some sort of similarly swifty one-liner that will create an array of UIButtons, instead of Ints.
my first thought was (0...4).map() { UIButton.buttonWithType(.Custom) } but if I do this (or if I replace the UIButton code with, say NSObject()) I get an error saying that "'Transition' does not have a member named 'map'". I can, say, do map() { "\($0)" } and get an array of strings.
My next thought was just to get an array of ints first, and then use map on those to return buttons, like:
let values: [UIButton] = (0...4)
.map() { $0 }
.map() { UIButton.buttonWithType(.Custom) }
but this gives me the same error. What am I doing wrong, here?

Okay, the solution came to me pretty quickly: I guess I can't quietly ignore the variable of the closure I'm passing in to the map function; I need to ignore it explicitly. So to get my array of 4 buttons, I can use an _ in the map function, like this:
var buttons = (0...4).map() {_ in UIButton.buttonWithType(.Custom) }
or what I actually ended up using:
lazy var buttons: [UIButton] = (0...4).map() { _ in
let button = UIButton.buttonWithType(.Custom) as UIButton
self.addSubview(button)
return button
}

Related

Append filtered string array to UILables - Swift

Long title! I do apologize.
Expected Outcome: Display uniqued string value in UILabel inside a UIView in a stackView. There may be multiple UIViews in the stackView. The stackView lives inside a tableCell. wow....
The views and cells are custom and I am not allowed to create sections. I have to work with what's in the existing codebase.
Issue I am stuck at trying to get the unique optional string values into the respective UILabels. I have a working extension to get unique items from an array. But I just don't know where to implement it, to get the unique values I need.
Code:
// Sample Model Structs
struct Parent {
var child: [Child]?
}
struct Child {
var childValue: String?
}
class TableViewCell {
var stackView = UIStackView()
func configureCellFrom(parent: Parent) {
/// Other code lives in the func to use the Parent struct.
if let child = parent.child {
if child.count > 1 {
tableCell.topLabel.text = "Multiple Child Values"
tableCell.ShowChildViewsButton.isHidden = false
for value in child {
let view = CustomUIView()
view.childValue.text = value.childValue.uniqued()
self.stackView.addArrangedSubview(view)
}
}
}
}
}
extension Sequence where Element: Hashable {
func uniqued() -> [Element] {
var set = Set<Element>()
return filter { set.insert($0).inserted }
}
}
Above Problem: Where I placed the uniqued() method, will parse out the individual characters in the string. So I know that it is one level too deep. What's the best way to achieve my required result?
The issue there is that uniqued method uses filter method declared on Sequence which will always return an array of the Sequence.Element in the case of a String an array of characters [Character]. You can simply initialize a new string with the array or characters or improve that method to support strings as well. To make that method support strings you need to extend RangeReplaceableCollection. There is a filter method declared on RangeReplaceableCollection which returns Self therefore if you filter a string it will return another string as I showed in this answer from the same post where you found the uniqued method you've shown in your question:
extension RangeReplaceableCollection where Element: Hashable {
var orderedSet: Self {
var set = Set<Element>()
return filter { set.insert($0).inserted }
}
}
Usage:
view.childValue.text = value.childValue?.orderedSet
Try this:
for (i, value) in child.enumerated() {
let view = CustomUIView()
view.childValue.text = value.childValue.uniqued()[i]
self.stackView.addArrangedSubview(view)
}

How to remove an element of a given custom type value from an array in Swift

I want to remove element of custom type value from an array.
I want to pass a variant instance to function to remove it from array, I don't want to use removeAtIndex().
var favoriteVariants: [Variant]
func removeVariant(variant: Variant)
{
}
If Variant is Equatable and you only want to remove the first one that matches:
if let idx = favoriteVariants.indexOf(variant) {
favoriteVariants.removeAtIndex(idx)
}
If it isn’t Equatable and you have some other matching criteria to find just one to remove:
let idx = favoriteVariants.indexOf {
// match $0 to variant
}
if let idx = idx {
favoriteVariants.removeAtIndex(idx)
}
(these are assuming Swift 2.0 – if 1.2, it’s find(favoriteVariants, variant) instead of indexOf, and there isn’t a version that takes a closure, though it’s not too hard to write one)
If there are multiple ones you want to remove in one go:
favoriteVariants = favoriteVariants.filter {
// criteria to _keep_ any given favorite
}
All of these could be wrapped in extensions if what you want to do is general enough to justify it.

SWIFT: "removeAtIndex" don't work with (sender:UIButton)

#IBOutlet var items: [UIButton]
#IBAction func itemsHidden(sender: UIButton) {
sender.hidden = true
items.removeAtIndex(sender)
}
Hello.
For example, i have array of items.
The code has the error: "Cannot invoke 'removeAtIndex' with an argument list of type (UIButton)".
What i need to do, that "removeAtIndex" works?
Thanks...
A removeAtIndex method expects to get an index as a parameter.
If you want to remove an object use func removeObject(_ anObject: AnyObject)
EDIT
There's no removeObject in a swift's array (only in NSMutableArray).
In order to remove an element, you need to figure out it's index first:
if let index = find(items, sender) {
items.removeAtIndex(index)
}
You don't tell us the class of your items object.
I assume it's an Array. If not, please let us know.
As Artem points out in his answer, removeAtIndex takes an integer index and remove the object at that index. The index must be between zero and array.count-1
There is no removeObject(:) method for Swift Array objects because Arrays can contain the same entry at more than one index. You could use the NSArray method indexOfObject(:) to find the index of the first instance of your object, and then removeAtIndex.
If you're using Swift 2, you could use the indexOf(:) method, passing in a closure to detect the same object:
//First look for first occurrence of the button in the array.
//Use === to match the same object, since UIButton is not comparable
let indexOfButton = items.indexOf{$0 === sender}
//Use optional binding to unwrap the optional indexOfButton
if let indexOfButton = indexOfButton
{
items.removeAtIndex(indexOfButton)
}
else
{
print("The button was not in the array 'items'.");
}
(I'm still getting used to reading Swift function definitions that include optionals and reference protocols like Generator so the syntax of the above may not be perfect.)

Extending Array to append SKTextures [duplicate]

This question already has answers here:
Is it possible to make an Array extension in Swift that is restricted to one class?
(4 answers)
Closed 7 years ago.
Being fairly new to Swift I decided I would look at extending Array (or more specifically [SKTexture] Arrays of SKTexture) with a function to add a specified number of frames from the application bundle.
// FRAMES
FuzzyRabbit_0001#2x.png
FuzzyRabbit_0002#2x.png
FuzzyRabbit_0003#2x.png
FuzzyRabbit_0004#2x.png
// CALL
var rabbitTextures = [SKTexture]()
self.rabbitTextures.textureFromFrames("FuzzyRabbit", count: 4)
My first attempt is listed below, I am getting the error Cannot invoke 'append' with an argument list of type '(SKTexture!)' which from looking at the function fuzzyPush is because I am trying to append an SKTexture rather than the generic T.
Is this possible, or am I limited by the fact that I don't want the function to be generic but rather specific to Arrays of SKTexture.
extension Array {
// ONLY SKTexture
mutating func textureFromFrames(imageName: String, count: Int) {
if !(self[0] is SKTexture) { return }
for index in 1...count {
let image = String(format: "\(imageName)_%04d", index)
let texture = SKTexture(imageNamed: image)
self.append(texture) // ERROR: Cannot invoke append with an argument list of type SKTexture!
}
}
// WORKS FINE
mutating func fuzzyPush(newItem: T) {
self.append(newItem)
}
}
I was just curious if this is something I could do with an extension, its not a problem as I have this as a function that takes 3 parameters (imageName, count, arrayToAppend) so I can quite easily use that.
This extension is not possible to write today. You cannot apply an extension method to only certain types of arrays.
There are two good solutions. You can use a HAS-A pattern by creating a struct (TextureList) that contains a [SKTexture], or you can use a function.
You can replace :
self.append(texture)
with
self.append(texture as T)
I checked this on an array of strings though and it worked.
About the first check add another check to see if the array is empty otherwise the self[0] is SKTexture will fail.
This is the code I tested on an online swift compiler (SKTexture was not available obviously) :
extension Array {
mutating func textureFromFrames(imageName: String, count: Int) {
for index in 1...count {
let image = String(format: "\(imageName)_%04d", index)
self.append(image as T)
}
}
}
var arr = Array<String>()
arr.textureFromFrames("testing", count:4)
for tmp in arr {
println("\(tmp)")
}

Get an Int out of an UILabel Swift

I have the problem, to have a high amount of buttons which have a number as their label, so i thought i could take the label as an integer instead of creating an action for every button?!
#IBAction func NumberInput(sender: UIButton) {
var input:Int = sender.titleLabel as Int
}
If you want to do this, you can convert the string to an Int by using string.toInt() such as:
if let input = sender.titleLabel?.text?.toInt() {
// do something with input
} else {
// The label couldn't be parsed into an int
}
However, I'd suggest either using UIView.tag or subclassing UIButton and adding an Int property to it to accomplish this, in case you ever change the display of your labels.
You should make sure that the text exists
var input:Int = (sender.titleLabel.text! as NSString).integerValue
You can't convert a UILabel to an Int. I think you want this instead:
var input : Int? = sender.titleLabel.text?.toInt()
Another way to convert a label in swift:
let num = getIntFromLabel(labelView)
connect all your buttons to 1 IBAction. then create the following variable and the set/get methods based on how you will use it.
note: "something" is a UILabel. The variable I wrote below should help you do conversions easily and with cleaner syntax. "newValue" comes with all setter methods. It basically takes into account any value that could possibly used to set "num" to a new value.
var num : Int {
get {
return Int(something!)!
}
set {
something.text = Int(newValue)
}
}
For Swift 3, what you can do is to directly convert it from an String input to an integer, like this
Int(input.text!)
And then, if for any reason, if you wish to print it out or return is as a String again, you can do
String(Int(input.text!)!)
The exclamation mark shows that it is an optional.

Resources