I recently started learning Swift which is my first attempt to learning how to program and I started my first app. I used several tutorials and so far I could solve every problem through research. Now I am stuck.
I want to create an app that can pick a random cocktail for me out of an array (cocktails) based on filters. To do so, I created 21 filters (cocktail.filter({!$o.orangeJuice}) for example. This takes all cocktails out of the array using orange juice.).
Creating the UI I added 21 UISwitches to toggle whether a filter has to be applied or not.
My randomize button works and there is a random cocktail name displayed but I can't get those UISwitches to work.
See my code:
var cocktailNoOrangeJuice = cocktails.filter({!$0.orangeJuice})
var cocktailNoLemonJuice = cocktails.filter({!$0.lemonJuice})
var cocktailNoAppleJuice = cocktails.filter({!$0.appleJuice})
var cocktailNoMaraJuice = cocktails.filter({!$0.maraJuice})
var cocktailNoLimeJuice = cocktails.filter({!$0.limeJuice})
var cocktailNoBananaJuice = cocktails.filter({!$0.bananaJuice})
var cocktailNoPeachJuice = cocktails.filter({!$0.peachJuice})
var cocktailNoCherryJuice = cocktails.filter({!$0.cherryJuice})
var cocktailNoJohanJuice = cocktails.filter({!$0.johanJuice})
var cocktailNoMangoJuice = cocktails.filter({!$0.mangoJuice})
var cocktailNoGrapefJuice = cocktails.filter({!$0.grapefJuice})
var cocktailNoTomatoJuice = cocktails.filter({!$0.tomatoJuice})
var cocktailNoCranbJuice = cocktails.filter({!$0.cranbJuice})
var cocktailNoBloodJuice = cocktails.filter({!$0.bloodJuice})
var cocktailNoPineapJuice = cocktails.filter({!$0.pineapJuice})
var cocktailNoCola = cocktails.filter({!$0.cola})
var cocktailNoSprite = cocktails.filter({!$0.sprite})
var cocktailNoBitter = cocktails.filter({!$0.bitter})
var cocktailNoTonic = cocktails.filter({!$0.tonic})
var cocktailNoGinger = cocktails.filter({!$0.ginger})
var cocktailNoAlc = cocktails.filter({!$0.noalc})
//this is a new array currently with the "noalc"-filter applied
var cocktailfiltered = cocktails.filter({!$0.noalc})
class ViewController: UIViewController {
// this is one of the UISwitches
#IBOutlet weak var lemon: UISwitch!
// The label for Cocktail output and the random button
#IBOutlet weak var ergebnis: UILabel!
#IBAction func random(sender: AnyObject) {
let randomIndex = Int(arc4random_uniform(UInt32(cocktailfiltered.count)))
ergebnis.text = (cocktailfiltered[randomIndex].name)
}
}
Please forgive me if this too silly. I found out how to pick up the state of a UISwitch (e.g lemon.on ...) but cannot use this information to apply a filter.
Any help is highly appreciated. Though I first hoped to be able to solve this on my own now it gets frustrating.
Notice that the cocktails are defined as members of a class and every ingredient such as orange juice throws a bool. Thus the filters are manually working. But not in the UI.
Edit: So this is the version right now. In my opinion it looks far better thanks to #vadian but causes my app to crash.
class ViewController: UIViewController {
let keys = ["lemonJuice", "limeJuice", "bananaJuice", "pineapJuice", "maraJuice", "mangoJuice", "orangeJuice", "appleJuice", "peachJuice", "bloodJuice", "grapefJuice", "tomatoJuice", "cranbJuice", "cherryJuice", "johanJuice", "cola", "sprite", "bitter", "tonic", "ginger", "noalc"]
var states = [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true]
#IBAction func changeState(sender: UISwitch) {
let index = sender.tag
states[index] = sender.on}
#IBOutlet weak var ergebnis: UILabel!
#IBAction func random(sender: AnyObject) {
var conditions = [String]()
for (index, state) in states.enumerate() {
if state {
conditions.append("(\(keys[index]) == TRUE)")
}
}
let format = conditions.joinWithSeparator(" AND ")
let predicate = NSPredicate(format:format)
let filtered = (cocktails as NSArray).filteredArrayUsingPredicate(predicate)
let randomIndex = Int(arc4random_uniform(UInt32(filtered.count)))
ergebnis.text = (filtered[randomIndex].name)
}
}
Suggestion:
Create two arrays, one for the names/keys of the Bool properties in your custom class
let keys = ["orangeJuice", "lemonJuice" ... ]
and one for the states of the corresponding switches
var states = [false, false, ...]
Assign tags to the UISwitches starting with zero in order of the keys array.
Create a change state IBAction and assign the action to all UISwitches
#IBAction func changeState(sender: UISwitch) {
let index = sender.tag
states[index] = sender.on
}
in the random function create an NSPredicate programmatically by a repeat loop to get all true values of the states array and the corresponding key of the keys array. Then filter the cocktail array by that predicate and get the random cocktail.
PS: For a good user experience get the filtered cocktails in the changeState function and inform the user in case no cocktail matches the chosen ingredients.
Update:
An example to create the predicate
var conditions = [String]()
for (index, state) in states.enumerate() {
if state {
conditions.append("(\(keys[index]) == TRUE)")
}
}
let format = conditions.joinWithSeparator(" AND ")
let predicate = NSPredicate(format:format)
Related
How can I not repeat the array when I click on the button in swift? I'm trying to generate fruits without them repeating.
import UIKit
class fruitrandomViewController: UIViewController {
#IBOutlet weak var nextfruitButton: UIButton!
#IBOutlet weak var fruitbox: UILabel!
#IBAction func fruitbutton(_ sender: UIButton) {
let array = ["Apple","Banana","Orange","Pinapple", "Plum", "Pear",]
let randomFruitgenerator = Int(arc4random_uniform(UInt32(array.count)))
fruitbox.text = array[randomFruitgenerator]
}
}
My suggestion is to use a Set and remove the random item from the set
var set = Set(["Apple","Banana","Orange","Pinapple", "Plum", "Pear"])
#IBAction func fruitbutton(_ sender: UIButton) {
if let fruit = set.randomElement() {
fruitbox.text = fruit
set.remove(fruit)
} else {
fruitbox.text = "" // or whatever to indicate that the set is empty
}
}
My suggest:
You can random each item 1 time
let originFruits = ["Apple","Banana","Orange","Pinapple", "Plum", "Pear"]
let array = originFruits
#IBAction func fruitbutton(_ sender: UIButton) {
...
let fruitRandom = array random
array delete fruitRandom
if (array empty) {
array = originFruits
}
}
You can check and remove in next time
let originFruits = ["Apple","Banana","Orange","Pinapple", "Plum", "Pear"]
let array = originFruits
let skipFruit = ""
#IBAction func fruitbutton(_ sender: UIButton) {
...
array = originFruits
array delete skipFruit
let fruitRandom = array random
skipFruit = fruitRandom
}
I think this will work, but I think it's Time Complexity can be O(n) as I am assuming that there is a possibility of randomElement return Apple every time and savedFruit is also Apple, so in that case, the Time Complexity will be O(n). A better workaround will be to remove that element from that array so, the next time the randomElement will be different for sure. Then, once it is different you can append the old one and remove the current one. I hope it makes sense to you, but for now this will work I think:
let array = ["Apple","Banana","Orange","Pinapple", "Plum", "Pear"]
var savedFruit = String()
func fetchRandomFruit() {
if let fruit = array.randomElement() {
if fruit != savedFruit { //Here it will check if the last element is same as the new randomElement
savedFruit = fruit
fruitbox.text = savedFruit
} else {
fetchRandomFruit()
}
}
}
#IBAction func fruitbutton(_ sender: UIButton) {
fetchRandomFruit()
}
Use fruitbox.text = array.randomElement() ?? "default value for if array is empty". This is not guaranteed not to repeat, but is random.
If you want to ensure it does not repeat, use this
var new = array.randomElement() ?? fruitbox.text
while new == fruitbox.text {
new = array.randomElement() ?? fruitbox.text
}
fruitbox.text = new
This WILL loop infinitely (until it crashes) if the array is empty.
I am trying to understand the logic of draggable collection view cells. It works with dummy data however, I couldn't figure out how to make it work with real data.
I couldn't know what title to give to the question, please feel free to edit/improve it
If I use this approach with a dummy array items and call the function
class TableViewController: UITableViewController, KDRearrangeableCollectionViewDelegate, UICollectionViewDataSource {
lazy var data : [[String]] = {
var array = [[String]]()
let images = [
"1.jpg", "2.jpg", "3.jpg"
]
if array.count == 0 {
var index = 0
var section = 0
for image in images {
if array.count <= section {
array.append([String]())
}
array[section].append(image)
index += 1
}
}
return array
}()
func moveDataItem(fromIndexPath : NSIndexPath, toIndexPath: NSIndexPath) {
let name = self.data[fromIndexPath.section][fromIndexPath.item]
self.data[fromIndexPath.section].removeAtIndex(fromIndexPath.item)
self.data[toIndexPath.section].insert(name, atIndex: toIndexPath.item)
print(self.data)
}
At this point print(self.data) prints the new order of the array items after dragging/rearranging.
Example print(self.data) log:
[["2.jpg", "1.jpg", "3.jpg"]]
Now I have my real data as and it gets appended by items after fetching from database..
var realImages = [NSURL]()
// I tried assigning `images` array inside `lazy var data` but received error:
lazy var data : [[String]] = {
var array = [[String]]()
let images = realImages // error here
...
Instance member realImages cannot be used on type TableViewController
What is the proper way of using my real array in that case?
(This explanation and its Git repo was great for understanding it). I simplified it even further with dummy data but I couldn't understand the logic of 'lazy var' and regular 'var' and how to make it work in this scenario
You need to use self explicitly like let images = self.realImages.
lazy var data : [[String]] = { [unonwed self] in
var array = [[String]]()
let images = self.realImages // error gone :)
(Note that we had to say [unowned self] in here to prevent a strong reference cycle)
I'm trying to load my data into a WatchKit table. Basically, set the text of the match label in each table group cell with the array of matchs I have.
I've got the data, and everything set up, but actually loading it into the table is where I'm stuck.
InterfaceController.swift:
var receivedData = Array<Dictionary<String, String>>()
var eventsListSO = Array<Event>()
#IBOutlet var rowTable: WKInterfaceTable!
func doTable() {
eventsListSO = Event.eventsListFromValues(receivedData)
rowTable.setNumberOfRows(eventsListSO.count, withRowType: "rows")
for var i = 0; i < self.rowTable.numberOfRows; i++ {
let row = rowTable.rowControllerAtIndex(i) as? TableRowController
for eventm in eventsListSO {
row!.mLabel.setText(eventm.eventMatch)
NSLog("SetupTableM: %#", eventm.eventMatch)
}
}
}
I was trying to do it in doTable because that seemed like best place to do this, and I think doTable is set up right, but I'm not sure? Not sure if I need to make the array an optional type or what.
Here is the referencing code if needed:
RowController.swift:
class TableRowController {
#IBOutlet var mLabel: WKInterfaceLabel!
#IBOutlet var cGroup: WKInterfaceGroup!
}
Event.swift:
class Event {
var eventTColor:String
var eventMatch:String
init(dataDictionary:Dictionary<String,String>) {
eventTColor = dataDictionary["TColor"]!
eventMatch = dataDictionary["Match"]!
}
class func newEvent(dataDictionary:Dictionary<String,String>) -> Event {
return Event(dataDictionary: dataDictionary)
}
class func eventsListFromValues(values: Array<Dictionary<String, String>>) -> Array<Event> {
var array = Array<Event>()
for eventValues in values {
let event = Event(dataDictionary: eventValues)
array.append(event)
}
return array
}
}
So I'm not sure if:
- doTable is set up right (can't be because eventsListSO.count is null)
The way you work with tables in WatchKit is a lot different than UIKit.
After you call setNumberOfRows you need to iterate over each row and get the RowController.
for var i = 0; i < self.rowTable.numberOfRows; i++ {
var row = self.rowTable.rowControllerAtIndex(i)
//setup row here
}
You can check Raywenderlich's tutorial about WatchKit: http://www.raywenderlich.com/96741/watchkit-tutorial-with-swift-tables-glances-and-handoff, it teach you how to show tables on your watch, hope this help!
Im currently implementing a like functionality in my app, and I can't seem to be able to get this append and retrieving to work. Here is my Button Action which is found in my ViewController file
var Liked = Favourite()
let factBook = FactBook()
#IBAction func favour() {
var currentQuote = factBook.factsArray[factIndex]
Liked.favouriteArray.append(currentQuote)
}
The Favourite struct is called from
import Foundation
struct Favourite {
var favouriteArray: [String] = []
}
(The factBook struct is the same thing except the array actually has elements inside.)
Now my goal is to get all this to display on a separate view controller called favouriteViewController:
import UIKit
class FavouriteViewController: UIViewController {
#IBOutlet var LikeQuote: UILabel!
var liked = Favourite()
override func viewDidLoad() {
super.viewDidLoad()
if liked.favouriteArray.count > 0 {
LikeQuote.text = liked.favouriteArray[0]
} else if liked.favouriteArray.count == 0 {
LikeQuote.text = "No Liked Quotes Found, Go Favour Some!"
}
}
Now when I hit the button, theoretically I should be able to append it to the favouriteArray and then be able to display it on my favouriteViewController file, however when I save it and then open viewcontroller file it defaults to the liked.favouriteArray.count=0 scenario and prints out the text no matter how many quotes I save. I just need an idea of what's going wrong in this process?
Update: If I put append Hello world into text it still does not append to element and evaluates the array value as 0.
The problem is that because you are not saving the data somewhere like into a database or file, you won't be able to retrieve it so the code you add into the viewDidLoad will not work The only way for it to work is when the UIButton was tapped check the code below. Hope that Helps
import Foundation
import UIKit
struct Favourite {
var favouriteArray = [String]()
}
class FavouriteViewController: UIViewController {
// let factBook = FactBook()
#IBOutlet var LikeQuote: UILabel!
var liked = Favourite()
override func viewDidLoad() {
super.viewDidLoad()
}
func updateContent(){
if liked.favouriteArray.count > 0 {
LikeQuote.text = liked.favouriteArray[0]
}
else if liked.favouriteArray.count == 0 {
LikeQuote.text = "No Liked Quotes Found, Go Favour Some!"
}
}
#IBAction func favour() {
// var currentQuote = liked.factsArray[factIndex]
// Liked.favouriteArray.append(currentQuote)
liked.favouriteArray.append("today is a new day ")
updateContent()
}
}
I'm new to programming and after doing some tutorials online I decided to make my own app with Xcode 7 to be able to run my app on my iPhone. But when I try to get input data from a text field and put it into a var it gives me an error. I have tried to use the following:
var initialChips = 0
var initialBlind = 0
#IBOutlet weak var txtBlind: UITextField!
#IBOutlet weak var txtChips: UITextField!
#IBOutlet weak var doneBtn: UIBarButtonItem!
#IBAction func doneBtn(sender: AnyObject) {
let initialChips:Int? = Int(txtChips.text)
let initialBlind:Int? = Int(txtBlind.text)
}
Xcode will ask to put a "!" after ".text" but after doing that it gives me a warning: "Initialization of immutable value 'initialChips' (or 'initialBlind' was never used: consider replacing with assignment to '_'or removing it".
What can I do to set a value to my var?
The thing is that the let initialChips:Int? = Int(txtChips.text) generates a new variable called initialChips (different from the variable from the class). The difference between let and var is that let is inmutable and var is not. You should do:
initialChips = Int(txtChips.text)
To assign the integer from the UITextField to your class variable rather than declaring a new inmutable variable (with the same name) and do nothing with it.
Of course, the same happens with initialBlind
EDIT:
Try the following:
#IBAction func doneBtn(sender: AnyObject) {
if let initialChips = Int(txtChips.text!) {
self.initialChips = initialChips
}else {
self.initialChips = 0
}
if let initialBind = Int(txtBind.text!) {
self.initialBind = initialBind
}else {
self.initialBind = 0
}
}
You should add the ! after the closing parenthesis, not after .text, like so:
let initialChips:Int? = Int(txtChips.text)!