Property observers Swift 4 - swift

struct Kitchen // Coordinator
{
var foodItems = [FoodItem] () // Collection of FoodItem objects.
var wastedItems = [WastedItem]() // Collection of WastedItem
var totalSpend = Double()
var currentSpend = Double()
var weeklyHouseholdFoodBudget = Double()
var wastageCost = Double()
init(aTotal:Double,aCurrent:Double,aBudget:Double,aWastage:Double)
{
self.totalSpend = aTotal
self.currentSpend = aCurrent
self.weeklyHouseholdFoodBudget = aBudget
self.wastageCost = aWastage
}
struct FoodItem : Equatable
{
var itemName = String()
var itemQuantity = Double()
var dateOfUse = String()
var unitOfMeasurement = String()
init(aName:String,aQuantity:Double,aDateOfUse:String,aUnit:String)
{
self.itemName = aName
self.itemQuantity = aQuantity
self.dateOfUse = aDateOfUse
self.unitOfMeasurement = aUnit
}
mutating func listFoodItems()
{
for item in foodItems
{
print("Item Name:", item.getName(),",",
"Qunatity:",item.getItemQuantity(),",",
"Unit:",item.getUnitOfMeasurement(),",",
"Date of use:",item.getDateOfUse())
}
}
mutating func removeFoodItem(aFood:FoodItem)
{
if let anIndex = foodItems.index(of: aFood)
{
foodItems.remove(at: anIndex)
print(aFood.getName(),"Has been removed")
}
}
mutating func useFood(aFoodItem:inout FoodItem,inputQty:Double)
{
if (aFoodItem.getItemQuantity()) - (inputQty) <= 0
{
self.removeFoodItem(aFood: aFoodItem)
}
else
{
aFoodItem.useFoodItem(aQty: inputQty)
}
}
***Updated****
The issue I am having is when I use a func listFoodItems() the updated attribute itemQuantity does not change. I would like to know how to update the collection so when I call the func listFoodItems() it displays value changes.
The removal is ok, when the func runs the collection removes the object.
The issue must be because I am using for item in foodItems to display, I need to reload it with updated values before I do this?
Thank you for any assistance.

Property observers are used on properties.
The simplest example of a property observer.
class Foo {
var bar: String = "Some String" {
didSet {
print("did set value", bar)
}
}
}
Now if you want to display some changes, you will need your own code in didSet method in order to do that. For example call reloadData().

Related

How to get the property names for `UIFont` (and other UIKit classes)?

This simple code:
func getProperties(object: AnyObject) -> [String] {
var result = [String]()
let mirror = Mirror(reflecting: object)
mirror.children.forEach { property in
guard let label = property.label else {
return
}
result.append(label)
}
return result
}
works for custom classes, e.g.:
class A {
var one: String
var two: Int
init() {
one = "hello"
two = 1
}
}
var a = A()
print(getProperties(object: a))
// prints ["one", "two"]
However the same code for UIFont (and other UIKit classes), returns nothing:
var font = UIFont.systemFont(ofSize: 10)
print(getProperties(object: font))
// prints []
I also tried an old-school extension for NSObject:
extension NSObject {
var propertyNames: [String] {
var result = [String]()
let clazz = type(of: self)
var count = UInt32()
guard let properties = class_copyPropertyList(clazz, &count) else {
return result
}
for i in 0..<Int(count) {
let property: objc_property_t = properties[i]
guard let name = NSString(utf8String: property_getName(property)) else {
continue
}
result.append(name as String)
}
return result
}
}
And again, it works for my custom classes (if they extend NSobject and the properties are marked as #objc):
class B: NSObject {
#objc var one: String
#objc var two: Int
override init() {
one = "hello"
two = 1
}
}
var b = B()
print(b.propertyNames)
// prints ["one", "two"]
but doesn't find any properties for UIFont.
If I look at the UIFont definition, the properties are seems defined like regular properties (or at least this is what Xcode shows:
open class UIFont : NSObject, NSCopying, NSSecureCoding {
//...
open var familyName: String { get }
open var fontName: String { get }
// etc
The type of the class is UICTFont though, not sure if it matters.
I would like to understand why this is not working, but my main question is more generic: is there any way to obtain the properties of the UIKit class (e.g. UIFont)?

How to toggle a boolean value using timer in Swift

I have a boolean called 'isMatched'. It is written in my Model. I use this boolean to show a view on a certain state of the view. However, I want the view to get hidden automatically after 0.5 second. I tried to use DispatchQueue.main.asyncAfter and also Timer, but always got error "Escaping closure captures mutating 'self' parameter". I have copied my Model below. Please advice me a solution.
I made the boolean var 'isMatched' true in the 'choose()' function.
import Foundation
struct WordMatchingGameModel<CardContent> {
private(set) var cards: Array<Card>
private(set) var words: Array<Word>
private(set) var gameDataSet: Array<String>
private(set) var matchedWord: String = ""
var isMatched: Bool = false
private var colors: [[String]] = [["AeroBlue", "AeroBlue"], ["BlueBell", "BlueBell"], ["PinkLavender", "PinkLavender"], ["Opal", "Opal"], ["CornflowerBlue", "CornflowerBlue"]]
mutating func choose(card: Card) {
if let choosenIndex = cards.firstIndex(matching: card) {
if !cards[choosenIndex].isTapped {
cards[choosenIndex].isTapped = true
cards[choosenIndex].bgColors = numberOfMatchedWords == 0 ? colors[0] : numberOfMatchedWords == 1 ? colors[1] : colors[2]
words.append(Word(id: cards[choosenIndex].id, content: cards[choosenIndex].content))
print(word)
if match() {
for index in 0..<cards.count {
if cards[index].isTapped { cards[index].isDisabled = true }
}
numberOfMatchedWords += 1
score += 10
matchedWord = word
isMatched = true
delay(interval: 0.5) {
self.isMatched = false
}
words.removeAll()
}
} else {
cards[choosenIndex].isTapped = false
cards[choosenIndex].bgColors = ["GradientEnd", "GradientStart"]
var tempWords = Array<Word>()
for index in 0..<words.count {
if words[index].id != cards[choosenIndex].id { tempWords.append(words[index]) }
}
words = tempWords
}
}
}
func delay(interval: TimeInterval, closure: #escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + interval, execute: closure)
}
func match() -> Bool {
for index in 0..<gameDataSet.count {
if gameDataSet[index] == word {
return true
}
}
return false
}
var word: String {
var array = Array<Character>()
for index in 0..<words.count {
array.append(words[index].content as! Character)
}
return String(array).capitalized
}
private(set) var numberOfMatchedWords: Int = 0
private(set) var score: Int = 0
init(numberOfCards: Int, gameDataSet: Array<String>, cardContentFactory: (Int) -> CardContent) {
cards = Array<Card>()
words = Array<Word>()
self.gameDataSet = gameDataSet
for index in 0..<numberOfCards {
let content = cardContentFactory(index)
self.cards.append(Card(id: index + 1, content: content))
}
cards.shuffle()
colors.shuffle()
}
struct Card: Identifiable {
var id: Int
var content: CardContent
var bgColors: [String] = ["GradientEnd", "GradientStart"]
var isTapped: Bool = false
var isDisabled: Bool = false
}
struct Word: Identifiable {
var id: Int
var content: CardContent
}
}
Create a reference for self inside the function and use that instant of self inside the delay function.
mutating func choose(card: Card) {
var createReference = self
....................
delay(interval: 0.5) {
createReference.isMatched = false
}
}

SwiftUI - Is it possible to change an ActionSheet button text after it is displayed?

I would like to show an ActionSheet containing InApp purchase objects the user can purchase.
But I want that sheet to contain the prices of such objects, like:
Object 1 ($1.99)
Object 2 ($2.99)
...
but the price is asynchronous, cause it has to be retrieved from the store.
So, I thought about doing this:
struct Package {
enum Packtype:String {
typealias RawValue = String
case obj1 = "com.example.object1"
case obj2 = "com.example.object2"
}
var productID:String = ""
#State var namePriceString:String = ""
init(productID:String) {
self.productID = productID
}
}
then, when I create the action sheet button I do this:
var obj1 = Package(productID: Package.Packtype.obj1.rawValue)
var obj2 = Package(productID: Package.Packtype.obj2.rawValue)
self.getPrices(packages:[obj1, obj2])
let obj1Button = ActionSheet.Button.default(Text(obj1.$namePriceString)) {
// do something with obj1
}
let obj2Button = ActionSheet.Button.default(Text(obj2.$namePriceString)) {
// do something with obj1
}
// build the actionsheet
later in the code:
func getPrices(packages:[Package]) {
let productIDS = Set(packages.map {$0.productID})
SwiftyStoreKit.retrieveProductsInfo(productIDS) { (answer) in
if answer.invalidProductIDs.first != nil { return }
let results = answer.retrievedProducts
if results.count == 0 { return }
for result in answer {
if let package = packages.filter({ ($0.productID == result.productIdentifier) }).first {
package.namePriceString = result.localizedTitle + "(" + "\(result.localizedPrice!)" + ")"
}
}
}
}
I have an error pointing to Text on the button creation lines saying
Initializer 'init(_:)' requires that 'Binding' conform to
'StringProtocol'
In a nutshell I need this:
I display the actionsheet. Its buttons contain no price.
I retrieve the prices
Actionsheet buttons are updated with the prices.
A possible solution is to return prices in a completion handler and only then display the action sheet:
struct ContentView: View {
#State var showActionSheet = false
#State var localizedPrices = [Package: String]()
var body: some View {
Button("Get prices") {
getPrices(packages: Package.allCases, completion: {
localizedPrices = $0
showActionSheet = true
})
}
.actionSheet(isPresented: $showActionSheet) {
let buttons = localizedPrices.map { package, localizedPrice in
ActionSheet.Button.default(Text(localizedPrice), action: { buy(package: package) })
}
return ActionSheet(title: Text("Title"), message: Text("Message"), buttons: buttons + [.cancel()])
}
}
}
func getPrices(packages: [Package], completion: #escaping ([Package: String]) -> Void) {
// simulates an asynchronous task, should be replaced with the actual implementation
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
let localizedPrices = Dictionary(uniqueKeysWithValues: packages.map { ($0, "\(Int.random(in: 1 ..< 100))") })
completion(localizedPrices)
}
}
func buy(package: Package) {
print("Buying \(package.rawValue)")
}
enum Package: String, CaseIterable {
case obj1 = "com.example.object1"
case obj2 = "com.example.object2"
}
This can be further tuned with loading animations etc...

RxSwift - How to reflect the number of item's count to TableView

I'm new to RxSwift. This is quite tricky.
I'm creating like ToDoList that views which are tableView and add-item view are separated by TabBarController.
I have successfully displayed the list array and added a new item into tableView.
I also wanted to display the number of array's count and favourite count in the view that has tableView so that I have displayed it by throwing a value with .just.
But displaying a value based on the result of the array displayed by SearchBar, the value is not reflected as I expected.
In MainViewModel, I made sure if I could get the number of array's count properly by print, but apparently the value was fine.
It is just not reflected in the View.
// Model
struct Item: Codable {
var name = String()
var detail = String()
var tag = String()
var memo = String()
var fav = Bool()
var cellNo = Int()
init(name: String, detail: String, tag: String, memo: String, fav: Bool, celllNo: Int) {
self.name = name
self.detail = detail
self.tag = tag
self.memo = memo
self.fav = fav
self.cellNo = celllNo
}
init() {
self.init(
name: "Apple",
detail: "ringo",
tag: "noun",
memo: "",
fav: false,
celllNo: 0
)
}
}
struct SectionModel: Codable {
var list: [Item]
}
extension SectionModel: SectionModelType {
var items: [Item] {
return list
}
init(original: SectionModel, items: [Item]) {
self = original
self.list = items
}
}
Singleton share class
final class Sharing {
static let shared = Sharing()
var items: [Item] = [Item()]
var list: [SectionModel] = [SectionModel(list: [Item()])] {
didSet {
UserDefault.shared.saveList(list: list)
}
}
let listItems = BehaviorRelay<[SectionModel]>(value: [])
}
extension Sharing {
func calcFavCount(array: [Item]) -> Int {
var count = 0
if array.count > 0 {
for i in 0...array.count - 1 {
if array[i].fav {
count += 1
}
}
}
return count
}
}
// MainTabViewController
class MainTabViewController: UIViewController {
#IBOutlet weak var listTextField: UITextField!
#IBOutlet weak var tagTextField: UITextField!
#IBOutlet weak var itemCountLabel: UILabel!
#IBOutlet weak var favCountLabel: UILabel!
#IBOutlet weak var favIcon: UIImageView!
#IBOutlet weak var infoButton: UIButton!
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
private lazy var viewModel = MainTabViewModel(
searchTextObservable: searchTextObservable
)
private let disposeBag = DisposeBag()
private var dataSource: RxTableViewSectionedReloadDataSource<SectionModel>!
override func viewDidLoad() {
super.viewDidLoad()
setupTableViewDataSource()
tableViewSetup()
listDetailSetup()
}
// create Observable searchBar.text to pass to ViewModel
var searchTextObservable: Observable<String> {
let debounceValue = 200
// observable to get the incremental search text
let incrementalTextObservable = rx
.methodInvoked(#selector(UISearchBarDelegate.searchBar(_:shouldChangeTextIn:replacementText:)))
.debounce(.milliseconds(debounceValue), scheduler: MainScheduler.instance)
.flatMap { [unowned self] _ in Observable.just(self.searchBar.text ?? "") }
// observable to get the text when the clear button or enter are tapped
let textObservable = searchBar.rx.text.orEmpty.asObservable()
// merge these two above
let searchTextObservable = Observable.merge(incrementalTextObservable, textObservable)
.skip(1)
.debounce(.milliseconds(debounceValue), scheduler: MainScheduler.instance)
.distinctUntilChanged()
return searchTextObservable
}
func setupTableViewDataSource() {
dataSource = RxTableViewSectionedReloadDataSource<SectionModel>(configureCell: {(_, tableView, indexPath, item) in
let cell = tableView.dequeueReusableCell(withIdentifier: "ListCell") as! ListCell
cell.selectionStyle = .none
cell.backgroundColor = .clear
cell.configure(item: item)
return cell
})
}
func tableViewSetup() {
tableView.rx.itemDeleted
.subscribe {
print("delete")
}
.disposed(by: disposeBag)
viewModel.dispItems.asObservable()
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
func listDetailSetup() {
viewModel.itemCountObservable
.bind(to: itemCountLabel.rx.text)
.disposed(by: disposeBag)
viewModel.favCountObservable
.bind(to: favCountLabel.rx.text)
.disposed(by: disposeBag)
}
}
MainTabViewModel
final class MainTabViewModel {
private let disposeBag = DisposeBag()
private let userDefault: UserDefaultManager
var dispItems = BehaviorRelay<[SectionModel]>(value: [])
private let shared = Sharing.shared
// lazy var itemCount = shared.list[0].list.count
// lazy var favCount = shared.calcFavCount
var itemCountObservable: Observable<String>
var favCountObservable: Observable<String>
init(searchTextObservable: Observable<String>,
userDefault: UserDefaultManager = UserDefault()) {
self.userDefault = userDefault
let initialValue = shared.list
shared.listItems.accept(initialValue)
dispItems = shared.listItems
// this part is to display the initil number -> success
var itemCount = shared.list[0].list.count
itemCountObservable = .just(itemCount.description + " items")
var favCount = shared.calcFavCount(array: shared.list[0].list)
favCountObservable = .just(favCount.description)
// this part is based on the searching result -> failure
searchTextObservable.subscribe(onNext: { text in
if text.isEmpty {
let initialValue = self.shared.list
self.shared.listItems.accept(initialValue)
self.dispItems = self.shared.listItems
}else{
let filteredItems: [Item] = self.shared.list[0].list.filter {
$0.name.contains(text)
}
let filteredList = [SectionModel(list: filteredItems)]
self.shared.listItems.accept(filteredList)
self.dispItems = self.shared.listItems
itemCount = filteredItems.count
self.itemCountObservable = .just(itemCount.description + " items")
favCount = self.shared.calcFavCount(array: filteredItems)
self.favCountObservable = .just(favCount.description)
print("\(itemCount) items") // the ideal number is in but not shown in the view
}
})
.disposed(by: disposeBag)
}
}
I removed unnecessary code but I mostly pasted a whole code for your understanding.
Hope you could help me.
Thank you.
I solved this issue anyway; the value was reflected.
the issue was that itemCountObservable was declared as observable and .just was used.
How .just works is to throw onNext once and it is completed, which means the change I did in searchTextObservable.subscribe(onNext~ is unacceptable.
So I shifted the itemCountObservable: Observable<String> to BehaviorRelay<String>that only onNext is thrown and not completed, then it works.
My understanding of this issue is that itemCountObservable: Observable<String> stopped throwing a value due to .just as I've written above.
Am I correct??
If you're familiar with the difference between Observable and BehaviorRelay, it would be appreciated if you could tell me.
Thanks.

How can I make this Stack struct define?

I want to make this Stack struct properly.
How can I define 'topItem'?
Is it right to show above "newstack"?
struct Stack {
var Items:[String] = []
mutating func push (item:String){
Items += [item]
}
mutating func pop() -> String {
return Items.removeLast()
}
var topItem =`enter code here`
}
var newStack = Stack()
newStack.push("HistoryListController")
newStack.push("HistoryDetailViewController")
newStack.push("HistoryTimelineViewController")
newStack.push("HistoryChartViewController")
newStack.Items
if let topVC = newStack.topItem {
print("Top View Controller is \(topVC)")
//HistoryChartViewController
}
newStack.pop()
if let topVC = newStack.topItem {
print("Top View Controller is \(topVC)")
//HistoryTimelineViewController
}
I'm very beginner in Swift.
This is my first code
First, items should be lowercase. Second, it's not clear what you want topItem to return. If you want the most recent object, you could implement it like this:
struct Stack {
var items = [String]()
mutating func push (item:String){
items += [item]
}
mutating func pop() -> String {
let result = items[0]
items.removeLast()
return result
}
var topItem : String? {
return items.last
}
}
var newStack = Stack()
newStack.push("HistoryListController")
newStack.push("HistoryDetailViewController")
newStack.push("HistoryTimelineViewController")
newStack.push("HistoryChartViewController")
newStack.topItem // "HistoryChartViewController"
newStack.items
If you want the first object, change it to return items.first.