Populating UIAlertController with UIAlertActions based on string array - arrays

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)
}

Related

Instance method 'contains' requires that 'UITextField' conform to 'StringProtocol'

I have a tableview that displays a sourceArray with a Name and Category properties. I want to filter the Category property of the sourceArray based on the users input from a UIAlertController but getting the error "Instance method 'contains' requires that 'UITextField' conform to 'StringProtocol'". Any help is greatly appreciated.
var sourceArray = [(Name: String, Category: String, Picture1: UIImage, Picture2: UIImage, Picture3: UIImage, Description: String)]()
#IBAction func filterButton(_ sender: UIBarButtonItem) {
var textField = UITextField()
let alert = UIAlertController(title: "Category", message: nil, preferredStyle: .alert)
let action = UIAlertAction(title: "Filter", style: .default) { (action) in
print(textField)
let filtered = sourceArray.filter({$0.Category.contains(textField)})
self.filteredArray = filteredArray.isEmpty ? sourceArray : filtered
self.tableview.reloadData()
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Biceps, Chest, Shoulders, etc."
textField = alertTextField
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
This issue I was having is that textField was not being recognized as a String. So I added a constant text as a String and assigned the textField.text to it like so.
#IBAction func filterButton(_ sender: UIBarButtonItem) {
var textField = UITextField()
let alert = UIAlertController(title: "Category", message: nil, preferredStyle: .alert)
let action = UIAlertAction(title: "Filter", style: .default) { (action) in
let text: String = textField.text!
let filtered = self.sourceArray.filter({$0.Category.contains(text)})
self.filteredArray = self.filteredArray.isEmpty ? self.sourceArray : filtered
self.tableview.reloadData()
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Biceps, Chest, Shoulders, etc."
textField = alertTextField
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}

Swift: Creating a button that shuffles an array

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() }

Swift: Delete an element in a dictionary inside of a document field Firestore (Cloud Database)

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.

Swift list getting and removing value

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.

Issues with transparent PNG in UIActivityViewController for FB Messenger and iMessage

I have been using the following code to call the UIActivityViewController for sharing stickers via the various social media apps:
if let image = sticker.getUIImage(), let imgData = UIImagePNGRepresentation(image) {
let activityVC = UIActivityViewController(activityItems: [imgData], applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = gesture.view
self.present(activityVC, animated: true, completion: nil)
} else {
ErrorHandler.handleError(STICKER_IMAGE_NOT_FOUND, sticker)
}
This code has been working fine until the most recent update to FB messenger (version 98.0). Now it shows an error "Couldn't load content". FB messenger appears to prefer a URL like this:
if let item = sticker.getImageURL() {
let activityVC = UIActivityViewController(activityItems: [item], applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = gesture.view
self.present(activityVC, animated: true, completion: nil)
} else {
ErrorHandler.handleError(STICKER_IMAGE_NOT_FOUND, sticker)
}
This works fine with FB Messenger but iMessage displays the transparent PNG with a black background.
I was looking at UIActivityViewControllerCompletionWithItemsHandler but the discussion states it runs after the activity, too late for what I need to do. I also tried creating a custom UIActivity returning UIActivityType.message for activityType but it was added on to the bottom of the controller rather than taking over the default.
Is there a way to intercept the selection of the item in UIActivityViewController so I can use the MFMessageComposeViewController and add the UIImagePNGRepresentation to the message and allow all the others to use the URL?
Is there a particular argument type that I can pass to UIActivityViewController that will correctly display transparent PNG with all the social apps?
TIA
Mike
Circling back to close this up. I eventually switched from using the UIActivityViewController universally to a system that customizes what is done for each type of service. I use BDGShare by Bob de Graaf. I build a list of the services that the app supports, show a button for each and then a switch to jump to each type of share. Just in case someone's working on this, here's what I came up with:
The types of sharing the app wants to support:
public enum ShareServiceType:Int {
case iMessage=0, facebook, twitter, instagram, whatsApp, facebookMessenger, email, more
}
A class to store information about the type of sharing service
public class ShareTargetVO: NSObject
{
var icon:String!
var label:String!
var type:ShareServiceType
init( serviceType:ShareServiceType, icon:String, label:String )
{
self.type = serviceType
self.icon = icon
self.label = label
}
}
A function in my social networking helper to populate the list of available services:
static func getShareTargetList(for sticker : ReeSticker) -> [ShareTargetVO] {
var services:Array<ShareTargetVO> = []
// Check to see which capabilities are present and add buttons
if (BDGShare.shared().isAvailable(forServiceType: SLServiceTypeFacebook)) {
services.append(ShareTargetVO(serviceType: ShareServiceType.facebook, icon: "Icon-Share-Facebook", label: "Facebook"))
}
// Checking for facebook service type because there's no availability check with FBSDK for messenger (could be rolled into the lib)
if (BDGShare.shared().isAvailable(forServiceType: SLServiceTypeFacebook) && !sticker.type.doesContain("video")) {
services.append(ShareTargetVO(serviceType: ShareServiceType.facebookMessenger, icon: "Icon-Share-Messenger", label: "Messenger"))
}
if (BDGShare.shared().isAvailable(forServiceType: SLServiceTypeTwitter)) {
services.append(ShareTargetVO(serviceType: ShareServiceType.twitter, icon: "Icon-Share-Twitter", label: "Twitter"))
}
if (BDGShare.shared().canSendSMS()) {
services.append(ShareTargetVO(serviceType: ShareServiceType.iMessage, icon: "Icon-Share-Messages", label: "Messages"))
}
if UIApplication.shared.canOpenURL(URL(string: "whatsapp://")! as URL) && !sticker.type.contains("video") {
services.append(ShareTargetVO(serviceType: ShareServiceType.whatsApp, icon: "Icon-Share-Whatsapp", label: "What's App?"))
}
if UIApplication.shared.canOpenURL(URL(string: "instagram://app")! as URL) && !sticker.type.contains("video") {
services.append(ShareTargetVO(serviceType: ShareServiceType.instagram, icon: "Icon-Share-Instagram", label: "Instagram"))
}
if (BDGShare.shared().canSendEmail()) {
services.append(ShareTargetVO(serviceType: ShareServiceType.email, icon: "Icon-Share-Mail", label: "Email"))
}
services.append(ShareTargetVO(serviceType: ShareServiceType.more, icon: "Icon-Share-More", label: "More"))
return services
}
A function in my view controller to populate a UICollectionView of buttons for sharing limited to those services that are returned from the list function:
func layoutShareButtons() {
let f = self.view.frame
let btnWidth = f.width * 0.82
let bannerWidth = btnWidth + 10
mask = UIView(frame: CGRect(x: 0, y: 0, width: f.width, height: f.height))
mask.backgroundColor = .black
mask.alpha = 0.3
self.view.addSubview(mask)
buttonList = SocialHelper.getShareTargetList(for: self.sticker)
let buttonGridLayout = UICollectionViewFlowLayout()
buttonGridLayout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
buttonGridLayout.itemSize = CGSize(width: 60, height: 60)
buttonGridLayout.scrollDirection = .horizontal
buttonListView = UICollectionView(frame: CGRect(x: (f.width - bannerWidth) / 2,
y: self.preview.frame.origin.y + self.preview.frame.height + 10,
width: bannerWidth,
height: 80),
collectionViewLayout: buttonGridLayout)
buttonListView.register(ShareButtonCell.self, forCellWithReuseIdentifier: "shareButtonCell")
buttonListView.dataSource = self
buttonListView.delegate = self
self.view.addSubview(buttonListView)
// cancel button for sharing view
// Button (added last to ensure it's on top of the z-order)
cancelButton = SimpleButton(frame: CGRect(x: (f.width - bannerWidth) / 2, y: self.buttonListView.frame.origin.y + self.buttonListView.frame.height + 10, width: bannerWidth, height: 52))
cancelButton.backgroundColor = UIColor(netHex:0x202020)
cancelButton.layoutComponent(0, label: "Cancel")
cancelButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.cancelButtonPressed(_:))))
self.view.addSubview(self.cancelButton)
}
UICollectionView didSelect handler (for better SoC depending on your app remove the "share" function to a separate class just for the share implementation, in this app the screen we're working with is specifically for share):
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.share(shareTarget: buttonList[indexPath.row])
}
Finally, a function call the correct share type:
func share(shareTarget: ShareTargetVO) {
// Params to submit to service
self.shareUrl = self.sticker.getStickerURL()
let textStr = "" // BDGShare supports a message passed in as well but we just send the sticker
// we need the NSData either way (sticker / video)
var ok = true
// try? is fine here because result is tested below
let urlData : Data? = try? Data(contentsOf: self.shareUrl as URL)
ok = (urlData != nil)
var img: UIImage? = nil
// if it's an image type then get it
if ok {
if (self.shareUrl.pathExtension.contains("png")) || (self.shareUrl.pathExtension.contains("jpg")) {
img = UIImage(data: urlData! as Data)
ok = (img != nil)
}
}
if !ok {
let alertCtrl = UIAlertController(title: "Error sending", message: "There was an error gathering the information for sending this sticker.", preferredStyle: .alert)
alertCtrl.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alertCtrl, animated: true, completion: nil)
return
}
switch shareTarget.type
{
case .iMessage:
BDGShare.shared().shareSMS(textStr, recipient: nil, image: img, data:urlData! as Data, imageName: "sendSticker.png", completion: {(SharingResult) -> Void in
// Handle share result...
self.handleShareResult(shareTarget.type, shareResult: SharingResult)
})
break
case .facebook:
BDGShare.shared().shareFacebook("", urlStr: "", image: img, completion: {(SharingResult) -> Void in
// Handle share result...
self.handleShareResult(shareTarget.type, shareResult: SharingResult)
})
break
case .twitter:
... more code for handling each type of share
}
}
So there are all the pieces I used to implement using BDGShare to get around the UIActivityViewController. BTW - the "More" option at the end of the list calls that UIActivityViewController so it's still available to the user if they so choose.
HTH, Mike

Resources