I am trying to create an UIAlertAction that is displayed after clicking on a sort button, that would shuffle the array. I tried this:
let action4 = UIAlertAction(title: "Shuffle", style: .default) { ( action: UIAlertAction) in self.array.sort { self.array.shuffle() }
self.tableView.reloadData() }
I used that to sort by names:
let action = UIAlertAction(title: "By name", style: .default) { ( action: UIAlertAction) in self.array.sort { $1.first_name > $0.first_name }
self.tableView.reloadData() }
Error: Cannot convert value of type '[Person]' to expected argument type 'Bool'
Just remove sort, you don't want sorting, you want shuffling only.
let action4 = UIAlertAction(title: "Shuffle", style: .default) { _ in self.array.shuffle() }
Related
So i have a nested array of a model:
let models = [[ButtonModel]]
struct ButtonModel: Identifiable {
let id = UUID()
let value: String
let style: ColorStyle
init(_ value: String, _ style: ColorStyle) {
self.value = value
self.style = style
}
}
Then i want to add this as a grid so i have a VStack in which i loop x amount of HStacks with buttons in it.
But because of some reason i get this error:
Cannot convert value of type '[[ButtonModel]]' to expected argument type 'Binding'
Generic parameter 'C' could not be inferred
VStack {
ForEach(viewModel.buttons, id: \.self) { buttons in
HStack(spacing: GridPoints.x2) {
Spacer()
ForEach(buttons) { buttonValue in
if buttonValue == "/" {
imageButton(for: Asset.Image) { viewModel.addValue(buttonValue) }
} else {
Button(buttonValue, action: { viewModel.addValue(buttonValue) })
.buttonStyle(customFont: .h3)
.background(Color.backgroundColor)
.styleText(style: TextStyle.h3)
}
}
Spacer()
}
.padding(GridPoints.x1)
}
}
Anyone know what this error is?
Edit
It looks like you made a mistake when using buttonValue too.
You should use it like this
ForEach(buttons) { button in
if button.value == "/" {
imageButton(for: Asset.Image) { viewModel.addValue(button.value) }
}
...
}
ForEach requires an array whose every element is Identifiable (or telling which one is the id to use using id: \.something).
Your problem is that if you use an array of array, this means that every element of the outer array is another array which is NOT conforming to Identifiable.
If you use ForEach(arrayOfArray, id: \.self), on the other hand, your telling that the identifier will be each element of the outer array that must conform to Hashable. Array conform to Hashable if its element does. So try to change your struct to
struct ButtonModel: Identifiable, Hashable { ... }
Alternate solution
You could change your structure to
struct ButtonsGroup: Identifiable {
let id = UUID()
let buttons: [ButtonModel]
}
Then you will be using it like this:
let groups = [ButtonsGroup]()
ForEach(groups) { group in
ForEach(group.buttons) { button in
Text(button.value)
}
}
Currently have a UITableView where there is an edit and delete button. Right now I trying to figure out how you can delete an element inside a map/dictionary from the database. For example, i want to remove:
dailyIntake { .
1568695516 {
amount : 12 .
timestamp : 1568695516.837234
}
Here is an image of my database:
Firestore Image
.
Here is my code in Swift: {
#objc func handleDeleteTap() {
print("Delete Button Tapped!")
let deleteOption = UIAlertAction(title: "Delete", style: .destructive) { (action) in
do {
// handle delete logic in the backend and update tableview
collectionReference(to: .intake).document(userUID).updateData([
"dailyIntake" : FieldValue.arrayRemove()
])
} catch let err {
print("Failed to Sign Out with Error:", err)
CustomAlert.showAlert(on: self, style: .alert, title: "Deletion Error", message: err.localizedDescription)
}
}
let cancelOption = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
CustomAlert.showAlert(on: self, style: .alert, title: "Delete current log?", message: nil, actions: [deleteOption, cancelOption], completion: nil)
}
I appreciate your time! Please help! Haha
Try and update the field with a transaction
First read the dictionary field, delete the element in the dictionary then update the field.
Docs here should help: https://firebase.google.com/docs/firestore/manage-data/transactions
You can do it by passing the element to arrayRemove()
FieldValue.arrayRemove(element)
So I figured it out on how to delete a certain value inside the dailyIntake map in Firestore (Cloud-DataBase).
#objc func handleDeleteTap() {
print("Delete Button Tapped!")
let deleteOption = UIAlertAction(title: "Delete", style: .destructive) { (action) in
do {
// handle delete logic in the backend and update tableview
collectionReference(to: .intake).document(userUID).updateData([
"dailyIntake.1568695516" : FieldValue.arrayRemove()
])
} catch let err {
print("Failed to Sign Out with Error:", err)
CustomAlert.showAlert(on: self, style: .alert, title: "Deletion Error", message: err.localizedDescription)
}
}
let cancelOption = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
CustomAlert.showAlert(on: self, style: .alert, title: "Delete current log?", message: nil, actions: [deleteOption, cancelOption], completion: nil)
}
With the "." symbol next to the key, it will actually remove a certain value in the map.
I have the following function, it iterates over an array of Foo, and creates a new array using the items in the original array, but with different colors:
extension Collection where Iterator.Element == Foo {
public func color(colors: [NSColor]) -> [Foo] {
var result: [Foo] = []
for item in self {
for color in colors {
let newItem = Foo(name: item.name, color: color)
result.append(newItem)
}
}
return result
}
}
This works, but if I use it on a subclass Bar of Foo, it still returns [Foo], not [Bar].
I could do something like this:
let colors: [NSColor] = // set colors
let colorArray = array.color(colors).map{ Bar($0.name, $.color)
But then I need to remember to do that every time I call it for a subclass of Foo.
So how can I adjust the function to make it work on subclasses of Foo as well?
EDIT
Based on the comment below, I tried a generic function:
public func color<T>(colors: [NSColor]) -> [T] {
var result: [T] = []
for item in self {
for color in colors {
let newItem = T(name: item.name, color: color)
result.append(newItem)
}
}
return result
}
That gives me the error:
Non-nominal type 'T' does not support explicit initialization
So I searched for that, and found I need to use init:
let newItem = T.init(name: item.name, color: color)
Now I get this error:
Type 'T' has no member 'init'
Foo and Bar have an init, but that doesn't help here. Am I on the right track?
EDIT 2:
Martin's answer below made me realize an error in my code: where Iterator.Element == Foo should have been where Iterator.Element: Foo
You can replace the “same-type constraint” by a “subclass constraint” and use the collections Element type instead of Foo in the implementation:
extension Collection where Element: Foo {
func color(colors: [NSColor]) -> [Element] {
var result: [Element] = []
for item in self {
for color in colors {
let newItem = Element(name: item.name, color: color)
result.append(newItem)
}
}
return result
}
}
Note that this requires Foo (and its subclasses) to have a “required init” method:
required init(name: String, color: NSColor)
Another option is to define a protocol
protocol P {
var name: String { get }
init(name: String, color: NSColor)
}
to which Foo (and its subclasses) conform, and use the constraint
extension Collection where Element: P
for the extension method.
In any case, a more concise implementation would be
extension Collection where Element: Foo {
func color(colors: [NSColor]) -> [Element] {
return self.flatMap { item in
colors.map { Element(name: item.name, color: $0) }
}
}
}
Re your edit: As a free generic method (instead of an extension method) it would be something like
func color<T: P>(items: [T], with colors: [NSColor]) -> [T] {
return items.flatMap { item in
colors.map { T(name: item.name, color: $0) }
}
}
using the protocol defined above. The compiler needs to know that an instance of T can be created from an item name and a color.
I've got a string array called languages, and I want to create as many UIAlertActions in a UIAlertController as there are elements in the array. I don't know how large the array will be, as the user can add languages to the array using the add option from the same UIAlertController.
loadLanguages() successfully loads userDefaults data into the languages array if there's any existing languages that've either been saved as the 2 starter languages on first app load and/or added by the user on subsequent uses of the app.
The add language option works, and is stored in userDfeaults (self.saveLanguages), and appended to the languages array.
However I'm not sure about creating UIAlertAction options for each language that is in the languages array. I've tried looping through the array to generate each menu item, as the languages array has the answer as to how many UIAlertActions should be displayed, but nothing appears.
After extensive searches I haven't come across anything covering this, but I'm sure there's an elegant approach.
FYI: languageChoiceButton is declared as:
var languageChoiceButton = UIAlertAction()
#objc func languageMenu(){
loadLanguages()
let chooseLanguageController = UIAlertController(title: "Vocabulary Tutor", message: "Choose a Language", preferredStyle: .actionSheet)
let addLanguage = UIAlertAction(title: "Add Language", style: .default, handler: { (action) -> Void in
let ac = UIAlertController(title: "Add a language", message: nil, preferredStyle: .alert)
ac.addTextField { textField in
textField.placeholder = "New language"
}
let submitAction = UIAlertAction(title: "Add", style: .default) { [unowned self, ac] (action: UIAlertAction!) in
self.newLanguage = ac.textFields?[0].text ?? ""
print("newLanguage: \(self.newLanguage)")
self.languages.append(self.newLanguage)
self.saveLanguages()
self.loadLanguages()
}
ac.addAction(submitAction)
ac.addAction(UIAlertAction(title: "Cancel", style: .cancel))
self.present(ac, animated: true)
})
chooseLanguageController.addAction(addLanguage)
for language in languages {
languageChoiceButton = UIAlertAction(title: language.capitalized, style: .default, handler: { (action) -> Void in
self.chosenLanguage = language
self.title = self.chosenLanguage.capitalized
print("Chosen language is: \(self.chosenLanguage)")
self.loadInitialValues()
chooseLanguageController.addAction(self.languageChoiceButton)
})
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel) {
(action:UIAlertAction!) in
print("Cancel button tapped")
}
chooseLanguageController.addAction(cancel)
self.navigationController!.present(chooseLanguageController, animated: true, completion: nil)
}
Try to make an array while using certain languages and make a loop afterwards to add each of them to the alerts.
#objc func languageMenu(){
loadLanguages()
let chooseLanguageController = UIAlertController(title: "Vocabulary Tutor", message: "Choose a Language", preferredStyle: .actionSheet)
let i = languages.count - 1
for n in 0...i{
chooseLanguageController.addAction(UIAlertAction(title: arrayLanguage[n].language, style: .default, handler: { (action) in
print(self. languages[n])
}))
}
self.present(chooseLanguageController, animated: true, completion: nil)
}
Swift's documentation of Remove from an array is "Removes and returns the element at the specified position." When I try it I don't get any values. If I do it in two steps it works fine. What am I missing?
Main code:
func GetItemAlert(s: String){
// Create an alert
let alert = UIAlertController(
title: "New item",
message: s,
preferredStyle: .alert)
// Add a text field to the alert for the new item's title
//alert.addTextField(configurationHandler: nil)
// Add a "cancel" button to the alert. This one doesn't need a handler
alert.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil))
// Add a "OK" button to the alert. The handler calls addNewItem()
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in
}))
// Present the alert to the user
self.present(alert, animated: true, completion: nil)
}
func pop()-> String{
let removeid = Int(arc4random_uniform(UInt32(TheList.count)))
let val1 = TheList[removeid]
let val2 = TheList.remove(at: removeid)
tableView.reloadData()
return ""
}
#IBAction func btnPop(_ sender: Any) {
GetItemAlert(s: pop())
}
Extention:
import Foundation
class HatTrick
{
var item: String
public init(item: String)
{
self.item = item
}
}
extension HatTrick
{
public class func preload() -> [HatTrick]
{
return [
HatTrick(item: "A"),
HatTrick(item: "B"),
HatTrick(item: "C"),
HatTrick(item: "D"),
HatTrick(item: "E")
]
}
}
You are correct in saying that remove "Removes and returns the element at the specified position."
var a = [0, 1, 2, 3] // Creates an array
let b = a.remove(at: 2) // we removed a value from a and stored it in b
print(b) // Prints 2
Maybe it doesn't show val2 in the debugger cause Xcode optimized them out. You can try to call a function with them and see whether its still optimized away.
Val1 is still referenced inside the Array until its removed in the next call, so retained? If you care you could file a bugreport.