UIAlert w/ text field to table - ios

My objective is to create a new row in the table in the master view titled with the input from the UIAlert stored in namePrompt. How would I achieve this?
Currently, when I try to make a new row (using the + button in the top-right hand corner of the iPad simulator, running iOS 8) it crashes the app. I'm not really sure what I have done wrong, and any help at all is appreciated.
Code:
func insertNewObject(sender: AnyObject) {
//prompt for kid name
var namePrompt = UIAlertController(title: "Add Child", message: "Please enter child name", preferredStyle: UIAlertControllerStyle.Alert)
namePrompt.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil))
namePrompt.addAction(UIAlertAction(title: "Add Child", style: UIAlertActionStyle.Default, handler: nil))
namePrompt.addTextFieldWithConfigurationHandler({(textField: UITextField!) in
textField.placeholder = "Johnny"
})
self.presentViewController(namePrompt, animated: true, completion: nil)
let childName: AnyObject = namePrompt.textFields![0]
//add new object w/ above name
objects.insertObject(childName, atIndex: 0)
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
I think that the problem is arising from the let childName: Any Object line, and/or the objects.insertObject line.
--EDIT--
When I build/run the project, no errors are given to me in the code, but when I try to add a new row, as soon as I press the + button, I get 0x1fbaff4: nopw %cs:(%eax,%eax) from Thread 1 . This is related to the line let object = objects[indexPath.row] as NSDate (in below snippet) line (the default for the objects.insertObject line above was an NSDAte). I changed the NSDate to NSString and the same thing happened.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let object = objects[indexPath.row] as NSString
cell.textLabel?.text = object.description
return cell
}

You seem to be assuming that the textFields of the UIAlertController will be filled in immediately after the alert is presented. That doesn't give the user any time to actually fill out the fields.
Put the let childName = namePrompt.textFields![0] line (and following) in one of your button actions instead and wait for the user to tap that button before reading the textField.
--EDIT--
Here is your code with my suggested modification. I made as few changes to your code as possible. Note that there is an if statement to guard against empty childNames, but it won't guard against someone entering a bunch of spaces. You should probably make that part more robust.
func insertNewObject(sender: AnyObject) {
//prompt for kid name
var namePrompt = UIAlertController(title: "Add Child", message: "Please enter child name", preferredStyle: UIAlertControllerStyle.Alert)
namePrompt.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil))
namePrompt.addAction(UIAlertAction(title: "Add Child", style: UIAlertActionStyle.Default, handler: { (alertAction) in
let childName = (namePrompt.textFields![0] as UITextField).text as String
if childName != "" {
self.objects.insert(childName, atIndex: 0)
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
}))
namePrompt.addTextFieldWithConfigurationHandler({(textField: UITextField!) in
textField.placeholder = "Johnny"
})
self.presentViewController(namePrompt, animated: true, completion: nil)
}

Related

Unable to tap background to close popover/actionSheet Swift 5 - Very weird

The code below allows the user to do a 2 finger swipe down on an imageView and thus presenting a popover/actionSheet. That process works fine. Normally it is possible to tap outside the popover/actionSheet to close it.
The problem is that once the popover/actionSheet is presented, it doesn't allow tapping the background to close the popover/actionSheet. You actually need to tap inside the popover/actionSheet to close it.
There are other places in the app that present a popover/actionSheet but these are presented using a simple button tap.
Here's the really weird scenario. If I do the 2 finger swipe on the imageView and open the popover/actionSheet, the inability to tap the backGround is broken on all the other popover/actionSheet in the app too. If I bypass the 2 finger swipe on the imageView all of the other popover/actionSheet work as normal.
I've stripped out all the code other than what's needed to present the popover/actionSheet. And I created a new project with on VC and one imageView so as to eliminate any possible conflict with cocoa pod, etc.
What is wrong with this code?
class ViewController: UIViewController
{
#IBOutlet weak var imageView_Outlet: UIImageView!
override func viewDidLoad()
{
super.viewDidLoad()
imageView_Outlet.isUserInteractionEnabled = true
let swipeGuesture = UISwipeGestureRecognizer(target: self, action: #selector(imageViewSwiped(recognizer:)))
swipeGuesture.numberOfTouchesRequired = 2
swipeGuesture.direction = .down
imageView_Outlet.addGestureRecognizer(swipeGuesture)
}
#objc func imageViewSwiped(recognizer: UISwipeGestureRecognizer)
{
let theAlert = UIAlertController(title: "Welcome Image", message: "Only one image can be saved as your welcome screen. The current image will automatically be replaced." , preferredStyle: .actionSheet)
let chooseImage = UIAlertAction(title: "Choose a New Image", style: .default, handler: { (okAction) in
})
let deleteBtn = UIAlertAction(title: "Delete the Current Image", style: .destructive, handler: { (deleteAction) in
})
let cancelBtn = UIAlertAction(title: "Cancel", style: .cancel) { (cancelAction) in
}
theAlert.addAction(cancelBtn)
theAlert.addAction(chooseImage)
theAlert.addAction(deleteBtn)
let popOver = theAlert.popoverPresentationController
popOver?.sourceView = self.imageView_Outlet
popOver?.sourceRect = self.imageView_Outlet.bounds
popOver?.permittedArrowDirections = .any
present(theAlert, animated: true)
}
}

Animate CollectionViewCell after deleting item

For my CollectionView I have this animation inside willDisplay :
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// Add animations here
let animation = AnimationFactory.makeMoveUpWithFade(rowHeight: cell.frame.height, duration: 0.5, delayFactor: 0.1)
let animator = Animator(animation: animation)
animator.animate(cell: cell, at: indexPath, in: collectionView)
}
This is how the animation works (I implemented it for CollectionView) if you need it for more info.
Probelm:
Inside my project the user can create and delete an item.
Right now the collectionView is not animating after deleting even though I am calling reloadData:
extension MainViewController: DismissWishlistDelegate {
func dismissWishlistVC(dataArray: [Wishlist], dropDownArray: [DropDownOption]) {
self.dataSourceArray = dataArray
self.dropOptions = dropDownArray
self.makeWishView.dropDownButton.dropView.tableView.reloadData()
// reload the collection view
theCollectionView.reloadData()
theCollectionView.performBatchUpdates(nil, completion: nil)
}
}
This is where I call the delegate inside my other ViewController:
func deleteTapped(){
let alertcontroller = UIAlertController(title: "Wishlist löschen", message: "Sicher, dass du diese Wishlist löschen möchtest?", preferredStyle: .alert)
let deleteAction = UIAlertAction(title: "Löschen", style: .default) { (alert) in
DataHandler.deleteWishlist(self.wishList.index)
self.dataSourceArray.remove(at: self.currentWishListIDX)
self.dropOptions.remove(at: self.currentWishListIDX)
// change heroID so wishlist image doesnt animate
self.wishlistImage.heroID = "delete"
self.dismiss(animated: true, completion: nil)
// update datasource array in MainVC
self.dismissWishlistDelegate?.dismissWishlistVC(dataArray: self.dataSourceArray, dropDownArray: self.dropOptions)
}
let cancelAction = UIAlertAction(title: "Abbrechen", style: .default) { (alert) in
print("abbrechen")
}
alertcontroller.addAction(cancelAction)
alertcontroller.addAction(deleteAction)
self.present(alertcontroller, animated: true)
}
When creating the animation works just fine. This is how my createDelegateFunction looks like this:
func createListTappedDelegate(listImage: UIImage, listImageIndex: Int, listName: String) {
// append created list to data source array
var textColor = UIColor.white
if Constants.Wishlist.darkTextColorIndexes.contains(listImageIndex) {
textColor = UIColor.darkGray
}
let newIndex = self.dataSourceArray.last!.index + 1
self.dataSourceArray.append(Wishlist(name: listName, image: listImage, wishData: [Wish](), color: Constants.Wishlist.customColors[listImageIndex], textColor: textColor, index: newIndex))
// append created list to drop down options
self.dropOptions.append(DropDownOption(name: listName, image: listImage))
// reload the collection view
theCollectionView.reloadData()
theCollectionView.performBatchUpdates(nil, completion: {
(result) in
// scroll to make newly added row visible (if needed)
let i = self.theCollectionView.numberOfItems(inSection: 0) - 1
let idx = IndexPath(item: i, section: 0)
self.theCollectionView.scrollToItem(at: idx, at: .bottom, animated: true)
})
}
Animation of IUCollectionView elements insertion and deletion is correctly done using finalLayoutAttributesForDisappearingItem(at:) and initialLayoutAttributesForAppearingItem(at:)
Little excerpt from Apple's documentation on finalLayoutAttributesForDisappearingItem(at:)
This method is called after the prepare(forCollectionViewUpdates:) method and before the finalizeCollectionViewUpdates() method for any items that are about to be deleted. Your implementation should return the layout information that describes the final position and state of the item. The collection view uses this information as the end point for any animations. (The starting point of the animation is the item’s current location.) If you return nil, the layout object uses the same attributes for both the start and end points of the animation.
I solved the problem by removing performBatchUpdates ... I have no idea why it works now and what exactly performBatchUpdates does but it works so if anyone wants to explain it to me, feel free :D
The final function looks like this:
func dismissWishlistVC(dataArray: [Wishlist], dropDownArray: [DropDownOption], shouldDeleteWithAnimation: Bool, indexToDelete: Int) {
if shouldDeleteWithAnimation {
self.shouldAnimateCells = true
self.dataSourceArray.remove(at: self.currentWishListIDX)
self.dropOptions.remove(at: self.currentWishListIDX)
// reload the collection view
theCollectionView.reloadData()
} else {
self.shouldAnimateCells = false
self.dataSourceArray = dataArray
self.dropOptions = dropDownArray
// reload the collection view
theCollectionView.reloadData()
theCollectionView.performBatchUpdates(nil, completion: nil)
}
self.makeWishView.dropDownButton.dropView.tableView.reloadData()
}

Programmatically grab a changing textField in an UIAlertController

Generally, to grab the text of a UITextField every time it is changed, you just drag an IBOutlet from the Storyboard file, and select the "editing has changed" option.
However, I want to grab the text of a UITextField in a UIAlertController, so the text field is created programmatically, not via storyboard.
How do I programmatically create a function that runs every time a textfield (in the Alert window) has changed?
What I need:
func textHasChanged(textField: TextField) {
print(textField.text)
}
If input is "cat"
Needed Output needs to be:
"c"
"ca"
"cat"
How would I achieve this?
Note: I've tried doing the .addtarget method:
textField.addTarget(self, action: Selector(self.textFieldDidChange(_:)), for: UIControlEvents.editingChanged)
with this function in the same file:
func textFieldDidChange(txtField: UITextField)
But I keep getting this error:
Value of type "name of my viewcontroller" has no member "textFieldDidChange"
I've looked up this question but most give me outdated answers. Thanks!
Try this:
#IBAction func displayAlert(_ sender : AnyObject) {
let alertController = UIAlertController(title: "Enter some text", message: "Please enter some text in the field below", preferredStyle: .alert)
alertController.addTextField()
alertController.addAction(UIAlertAction(title: "OK", style: .default) { action in
print("User pressed OK")
})
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel) { action in
print("User pressed Cancel")
})
if let textField = alertController.textFields?.first {
textField.addTarget(self, action: #selector(ViewController.textHasChanged), for: .editingChanged)
}
self.present(alertController, animated: true)
}

How to adjust the TableView height when one of the item got deleted

Initially i wanted to create a UITableView which has a dynamic height according to its content size and followed a question of stackoverflow to overcome the situation. So what implemented after going through that question:
func autoAdjustToTableView(){
numberTableView.frame = CGRect(x: numberTableView.frame.origin.x, y: numberTableView.frame.origin.y, width: numberTableView.frame.size.width, height: numberTableView.contentSize.height)
}
And in the answer of that question it was suggested that i should call this thing from viewDidAppear and also from viewDidLayoutSubViews and also he called
tableView.reloadData()
in viewDidLayoutSubviews
So my first question why would i have to code in my viewDidLayoutSubviews ??
Though its working fine until the moment i am removing an item from the tableView and calling the same function is not getting me the dynamic change. This is where i m removing it:
let alertController = UIAlertController(title: "title", message: "message", preferredStyle:UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.default)
{ action -> Void in
return
})
alertController.addAction(UIAlertAction(title: "Remove", style: UIAlertActionStyle.destructive)
{ action -> Void in
self.numberArray.remove(at: indexPath.row)
self.autoAdjustToTableView()
self.numberTableView.reloadData()
})
self.present(alertController, animated: true, completion: nil)
}
What needs to be done to change its height at removing item ??
First you need to call reloadData to change cells in tableView. The you need to call autoAdjustToTable view to change the height.
self.numberArray.remove(at: indexPath.row)
self.numberTableView.reloadData()
self.autoAdjustToTableView()

UIAlertController/ActionSheet crashes on iPad, how to pass sender?

I want to display an ActionSheet on both, iPhone and iPad devices. While my code works properly on iPhone's, it doesn't on iPad's. The reason for this is that I need to specify a location where to display the popover. As a result, I tried to used the code proposed in this answer. The problem that I currently have is, that I do not know how to pass the argument sender to the method.
For example, I have a UITableViewRowAction which when clicked should display the ActionSheet:
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let rowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Action", handler:{action, indexpath in
print("Action for element #\(indexPath.row).");
self.displayActionSheet()
});
return [rowAction];
}
Which argument is sender in my case? I am not able to pass rowAction to displayActionSheet(), because the variable is used within its own initial value. I also tried to pass self.displayDeleteLicencePlateActionSheet(self.items[indexPath.row]), but same result – I always end up in the else clause of the guard expression:
guard let button = sender as? UIView else {
print("sender empty")
return
}
I want to display also an ActionSheet when clicking on an UIBarButtonItem:
let myButton = UIBarButtonItem(title: "FooBar", style: .Plain, target: self, action: #selector(SecondViewController.displaySecondActionSheet(_:)))
But same result. How can this be done?
Please refer following code to fix your issue.
TARGET_OBJECT will be your sender where from you want to show an alert.
func showAlert() {
let alert = UIAlertController(title: "Title", message: "Message text", preferredStyle: .alert)
let actionOK = UIAlertAction(title: "OK", style: .default) { (alertAction) in
}
alert.addAction(actionOK)
if let popoverController = alert.popoverPresentationController {
popoverController.sourceView = self.TARGET_OBJECT // TARGET_OBJECT will be your sender to show an alert from.
popoverController.sourceRect = CGRect(x: self.TARGET_OBJECT.frame.size.width/2, y: self.TARGET_OBJECT.frame.size.height/2, width: 0, height: 0)
}
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)
}
Please check attached image it will appear as you want.

Resources