Im using this function to find which place in the array the title has, I'm wondering if it's possible to do this in a better/faster way.
func findId(title:String){
var id = 0
for restaurant in restaurantObjects{
if (restaurant.name == title){
self.idClicked = id
}
else{
id++
}
}
}
The function returns an optional Int, it's nil if the index could not be found.
func findId(title:String) -> Int? {
return restaurantObjects.indexOf{ $0.name == title}
}
struct Restaurant {
let name : String
init(_ name: String) {
self.name = name
}
}
let restaurantObjects : [Restaurant] = [Restaurant("foo"), Restaurant("foofoo"), Restaurant("bar"), Restaurant("foo")]
let title = "bar"
if let pos = restaurantObjects.indexOf({ $0.name == title }) {
print("Found \(title) at index \(pos)") // Found bar at index 2
}
Alternatively, make an array extension for elements of type Restaurant, allowing you to simply call .findFirstId(title: String) on you array of Restaurant objects.
struct Restaurant {
var name : String
init(_ name: String) {
self.name = name
}
}
protocol RestaurantObject {
var name : String { get }
}
extension Restaurant : RestaurantObject {}
extension Array where Element : RestaurantObject {
func findFirstId(title: String) -> Int? {
return self.indexOf { $0.name == title }
}
}
/* Example */
let restaurantObjects : [Restaurant] = [Restaurant("foo"), Restaurant("foofoo"), Restaurant("bar"), Restaurant("foo")]
let title = "bar"
restaurantObjects.findFirstId(title) // 2
Related
In the following example I am saving a String with #AppStorage:
struct ContentView: View {
#AppStorage("text") private var text = ""
var body: some View {
Button("Append text: \(text)") {
text.append("APPEND")
}
}
}
But I want to save a unique String array, something like this:
#AppStorage("text") #State private var customer = [CustomerId]()
//
struct CustomerId: Identifiable {
let id = UUID()
var number: String
init(_ number: String) {
self.number = number
}
//
Button {
customer
.append(CustomerId("New Id"))
}
It's highly recommended to move the entire code to modify the model into an extra class called view model.
The #AppStorage property wrapper reads and writes a JSON string.
The #Published property wrapper updates the view.
In the init method the saved JSON string is decoded to [CustomerId].
The method appendCustomer appends the items, encodes the array to JSON and writes it to disk.
class ViewModel : ObservableObject {
#AppStorage("text") var text = ""
#Published var customer = [CustomerId]()
init() {
customer = (try? JSONDecoder().decode([CustomerId].self, from: Data(text.utf8))) ?? []
}
func appendCustomer(_ sustomerId : CustomerId) {
customer.append(sustomerId)
guard let data = try? JSONEncoder().encode(customer),
let string = String(data: data, encoding: .utf8) else { return }
text = string
}
}
struct ContentView : View {
#StateObject private var model = ViewModel()
#State private var counter = 0
var body: some View {
VStack{
ForEach(model.customer) { item in
Text(item.number)
}
Button("Append text: New Id \(counter)") {
model.appendCustomer(CustomerId(number: "New Id \(counter)"))
counter += 1
}
}
}
}
Sorry, I'm a noob at Swift and still learning.
I'm getting the following error message from Xcode for the following swift code: "Cannot convert value of type 'Town?.Type' (aka 'Optional.Type') to expected argument type 'Town?'"
class Vampire: Monster {
var vampirePopulation: [Vampire] = []
override func terrorizeTown() {
if town?.population > 1 {
town?.changePopulation(-1)
} else {
town?.population = 0
}
vampirePopulation.append(Vampire(town: Town?, monsterName: String))
print("\(vampirePopulation.count) vampires")
super.terrorizeTown()
}
}
Here is the Monster Class:
import Foundation
class Monster {
static let isTerrifying = true
class var spookyNoise: String {
return "Grrr"
}
var town: Town?
var name = String ()
var victimPool: Int {
get {
return town?.population ?? 0
}
set(newVictimPool) {
town?.population = newVictimPool
}
}
init(town: Town?, monsterName: String) {
self.town = town
name = monsterName
}
func terrorizeTown() {
if town != nil {
print("\(name) is terrorizing a town!")
}else {
print("\(name) hasn't found a town to terrorize yet..")
}
}
}
Here is the Town struct:
import Foundation
struct Town {
var mayor: Mayor?
let region: String
var population: Int {
didSet(oldPopulation) {
if population < oldPopulation
{
print("The population has changed to \(population) from \
(oldPopulation).")
mayor?.mayorResponse()
}
}
}
var numberOfStoplights: Int
init(region: String, population: Int, stoplights: Int) {
self.region = region
self.population = population
numberOfStoplights = stoplights
}
init(population: Int, stoplights: Int) {
self.init(region: "N/A", population: population, stoplights:
stoplights)
}
enum Size {
case Small
case Medium
case Large
}
var townSize: Size {
get {
switch self.population {
case 0...10000:
return Size.Small
case 10001...100000:
return Size.Medium
default:
return Size.Large
}
}
}
func printTownDescription () {
print("Population: \(population); number of stoplights: \
(numberOfStoplights); region: \(region)")
}
mutating func changePopulation(_ amount: Int) {
population += amount
}
}
Why am I receiving this error message?
The error is pretty clear
Cannot convert value of type 'Town?.Type' (aka 'Optional.Type') to expected argument type 'Town?
means that you are passing a type rather than the instance of the type.
Instead of Town? pass town:
vampirePopulation.append(Vampire(town: town, monsterName: name))
let's assume that my model has the following:
import Foundation
class Model {
var userName = [String]()
var userAnswer = [String]()
var userInfo = [String]()
var name:String
var answer:String
init(){
self.name = ""
self.answer = ""
}
func addUserInfo(name:String, answer:String) -> Void {
userName.append(name)
userAnswer.append(answer)
}
}
What would one do to transform this model to have userName, and userAnswer become one joined dictionary? where the function addUserInfo would still work. Thanks!
you can use a funcion return a array or a new property with only get methord,just like this
var userNamesAndUserAnswers:[[String:String]]
{
get
{
var values:[[String:String]]=[]
for index in 0..<userName.count
{
values.append(["name":userName[index],"answer":userAnswer[index]])
}
return values
}
}
You can just have a Dictionary that represents user names mapped to user answers like this:
class Model {
var userInfo = [String:String]()
func addUserInfo(name:String, answer:String) {
userInfo[name] = answer
}
}
There's no need to import Foundation since you're not using any Foundation objects.
If you want a [String: String]:
var dict: [String: String] {
var result: [String: String] = [:]
zip(userName, userAnswer).forEach { result[$0.0] = $0.1 }
return result
}
Or if you want [[String: String]]:
var dict2: [[String: String]] { return zip(userName, userAnswer).map { [$0.0: $0.1] } }
I'm designing an iOS App using Swift. I have a View Controller, DisplayBrand that's displaying data fetched from a backend. I have made another class, FetchAndParseBrand, for fetching and parsing data from the backend.
The view controller is calling a method, getBrand, in the fetching class. This method then revieces data asynchronously from the backend.
How do I return the received data to the View Controller? I guess there are multiple ways of solving this problem, do you have any suggestion for the best practice?
Here is some code for context:
DisplayBrand
class DisplayBrand: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var brand = Brand()
FetchAndParseBrand().getBrand()
self.updateUI()
}
...
}
FetchAndParseBrand:
class FetchAndParseBrand: NSObject {
// Fetches one brand on static url
func getBrand() -> Brand{
let qos = Int(QOS_CLASS_USER_INITIATED.value)
var jsonError: NSError?
var brand = Brand()
if let url = NSURL(string: "http://backend.com/api/brand")
{
dispatch_async(dispatch_get_global_queue(qos, 0))
{
// simulate long load delay
println("going to sleep")
sleep(2)
if let brandData = NSData(contentsOfURL: url)
{
dispatch_async(dispatch_get_main_queue())
{
if let jsonBrand = NSJSONSerialization.JSONObjectWithData(brandData, options: nil, error: &jsonError) as? NSDictionary {
if let newName = jsonBrand.valueForKey("Name") as? String {
brand.name = newName
}
if let newGender = jsonBrand.valueForKey("Gender") as? Int {
if newGender == 0 {brand.gender = "male"}
else if newGender == 1 {brand.gender = "female"}
}
if let newId = jsonBrand.valueForKey("Id") as? Int {
brand.id = newId
}
brand.printBrand()
}
}
}
}
}
return brand
}
}
Brand
class Brand: NSObject {
var id: Int?
var name: String?
var image: UIImage?
var gender: String?
func printBrand() {
if id != nil { println("ID:\t\t\(self.id!)") }
if name != nil { println("Name:\t\(self.name!)") }
if image != nil { println("Image:\t\(self.image?.description)") }
if gender != nil { println("Gender:\t\(self.gender!)") }
}
}
You can use a completion handler for this, this will return the variable when it is read:
class FetchAndParseBrand: NSObject {
class func getBrand(completion: (brand: Brand) -> ()) {
let qos = Int(QOS_CLASS_USER_INITIATED.value)
var jsonError: NSError?
var brand = Brand()
if let url = NSURL(string: "http://backend.com/api/brand")
{
dispatch_async(dispatch_get_global_queue(qos, 0))
{
// simulate long load delay
println("going to sleep")
sleep(2)
if let brandData = NSData(contentsOfURL: url)
{
dispatch_async(dispatch_get_main_queue())
{
if let jsonBrand = NSJSONSerialization.JSONObjectWithData(brandData, options: nil, error: &jsonError) as? NSDictionary {
if let newName = jsonBrand.valueForKey("Name") as? String {
brand.name = newName
}
if let newGender = jsonBrand.valueForKey("Gender") as? Int {
if newGender == 0 {brand.gender = "male"}
else if newGender == 1 {brand.gender = "female"}
}
if let newId = jsonBrand.valueForKey("Id") as? Int {
brand.id = newId
}
completion(brand: brand)
brand.printBrand()
}
}
}
}
}
}
}
Then you can call the function like so:
FetchAndParseBrand.getBrand { (brand) -> () in
println(brand)
}
there are many ways. with callback function and delegates. you can also use singleton if you will call back only to a specific view controller:
class DisplayBrand: UIViewController {
static var sharedInstance :DisplayBrand?
override func viewDidLoad() {
super.viewDidLoad()
DisplayBrand.sharedInstance = self;
}
.
.
.
//on async over:
displayBrand.sharedInstance?.someFunction();
There is a major issue in your code:
FetchAndParseBrand.getBrand() is supposed to be a class method and has no parameter
The easiest way to get a asynchronous callback is a closure
Try this
Class DisplayBrand
class DisplayBrand: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
FetchAndParseBrand.getBrand() { (brand) -> () in
// do something with variable brand
self.updateUI() }
}
...
}
Class FetchAndParseBrand
class FetchAndParseBrand: NSObject {
// Fetches one brand on static url
class func getBrand(completion: (Brand)->()) {
let qos = Int(QOS_CLASS_USER_INITIATED.value)
var jsonError: NSError?
var brand = Brand()
if let url = NSURL(string: "http://backend.com/api/brand")
{
dispatch_async(dispatch_get_global_queue(qos, 0))
{
// simulate long load delay
println("going to sleep")
sleep(2)
if let brandData = NSData(contentsOfURL: url)
{
dispatch_async(dispatch_get_main_queue())
{
if let jsonBrand = NSJSONSerialization.JSONObjectWithData(brandData, options: nil, error: &jsonError) as? NSDictionary {
if let newName = jsonBrand.objectForKey("Name") as? String {
brand.name = newName
}
if let newGender = jsonBrand.objectForKey("Gender") as? Int {
if newGender == 0 {brand.gender = "male"}
else if newGender == 1 {brand.gender = "female"}
}
if let newId = jsonBrand.objectForKey("Id") as? Int {
brand.id = newId
}
brand.printBrand()
completion(brand)
}
}
}
}
}
}
}
Side note: valueForKey is a key value coding method. The standard method to get a value from a dictionary by key is objectForKey or use subscripting
I'm trying to do the following in a playground to assign an enum type based on a string, but getting an error in the changeType function. How can I get this to work properly?
enum TransactionType {
case purchase,charge
case deposit,payment
func description() -> String {
switch self {
case .purchase:
return "purchase"
case .charge:
return "charge"
case .deposit:
return "deposit"
case .payment:
return "payment"
}
}
func typeFromString(value:String) -> TransactionType {
switch value {
case "charge":
return .charge
case "deposit":
return .deposit
case "payment":
return .payment
default:
return .purchase
}
}
}
class Tester {
var transactionType = TransactionType.purchase
func changeType() {
transactionType = TransactionType.typeFromString("charge")
}
}
var tester = Tester()
print(tester.transactionType.description())
tester.changeType()
print(tester.transactionType.description())
The solution is simpler than you think:
enum TransactionType : String {
case purchase = "purchase", charge = "charge"
case deposit = "deposit", payment = "payment"
}
class Tester {
var transactionType = TransactionType.purchase
func changeType() {
transactionType = TransactionType.fromRaw("charge")!
}
}
var tester = Tester()
print(tester.transactionType.toRaw())
tester.changeType()
print(tester.transactionType.toRaw())
The trick is to set a raw value of String type, which defines the type associated to each enum case.
More info Raw Values in Enumerations
You can define the typeFromString method as static in order to avoid complications with optional values. After all, it just contains constants anyway. Simply add the word static before the func definition.
static func typeFromString(value:String) -> TransactionType {