I am working on an application that saves user's created images to the photo library. I need to attach a user name to each saved image so that I can recall these later. As far as I understand (please correct me if I am wrong), if I am saving to the photo library, I can't change the actual file name, so a way around this is to change modify the metadata and write the user name to the description for instance. However, I am completely stuck on how to accomplish this in Swift and I have searched everywhere for an answer. The code I have to save each UIImage is as follows: I have the save option and the input for a user name presented in an alert view.
// Create the alert controller
let alertController = UIAlertController(title: "Finished?", message: "Is this your attempt?", preferredStyle: .Alert)
// Create the actions
let okAction = UIAlertAction(title: "Yes, save", style: UIAlertActionStyle.Default) {
UIAlertAction in
NSLog("OK Pressed")
if let image = self.mainImageView.image{
UIImageWriteToSavedPhotosAlbum(image,self,Selector("image:withPotentialError:contextInfo:"),nil)
}
}
let cancelAction = UIAlertAction(title: "No", style: UIAlertActionStyle.Cancel) {
UIAlertAction in
NSLog("Cancel Pressed")
self.mainImageView.image = nil
}
alertController.addTextFieldWithConfigurationHandler { (textField : UITextField!) -> Void in
textField.placeholder = "Enter User Name"
self.inputTextField = textField
}
// Add the actions
alertController.addAction(okAction)
alertController.addAction(cancelAction)
// Present the controller
self.presentViewController(alertController, animated: true, completion: nil)
And I have another function for the image, which is as follows:
func image(image: UIImage, withPotentialError error: NSErrorPointer, contextInfo: UnsafePointer<()>) {
NSLog("\(self.inputTextField.text)")
let alertController = UIAlertController(title: "Success!", message:"Your image was saved", preferredStyle: .Alert)
//Some additional code here, specific to another part of the app that has to occur after the image is saved
self.presentViewController(alertController, animated: true){}
}
This code works to save an image, however I am completely at a loss for how to add anything to the metadata of an image. If anyone has any suggestions or advice it would be greatly appreciated and please let me know if anything is unclear and I can edit or clarify in the comments.
Related
I have an UIAlertController that allows a user to add a name and description of their smart advice. I would now like for the UIAlertController to allow the user to upload an image from their camera roll. I don't have any database or coredata enabled (Do I need to?). Here is my UIAlertController:
#IBAction func refreash(_ sender: AnyObject) {
let alert = UIAlertController(title: "Add Smart Device", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alert.addTextField(configurationHandler: { textField in
textField.placeholder = "Enter Name of the Smart Device Here"
})
alert.addTextField(configurationHandler: { textField in
textField.placeholder = "Enter Desc of the Smart Device Here"
})
// I want something like alert.addImagePicker here
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
if let name = alert.textFields?.first?.text,
let desc = alert.textFields?[1].text {
self.mySmartDeviceList.addSmartDevice(name: name, desc: desc, image: "robot.jpg")
let indexPath = IndexPath (row: self.mySmartDeviceList.getCount() - 1, section: 0)
self.cityTable.beginUpdates()
self.cityTable.insertRows(at: [indexPath], with: .automatic)
self.cityTable.endUpdates()
}
}))
self.present(alert, animated: true)
}
See how it says that the image is just "robot.jpg"? I want that to be the image that the user uploaded.
If your app allow user use offline, u need to use local database to save it by id,name, And save file in local by id, then upload those picture when online.
I suggest do not save NSdata in CoreData.
So I'm using Swift 5 and working with core data. I'm trying to add validation to the textfields through the UIAlertView.
To give clarification to what my code already does:
User presses a button which the iOS Photo Library Interface Pops up.
Once photo is selected a UIAlertViewpops up and asks me two enter texts in two textfields.
Once that is done, the data is submitted to the database and displays the image and the texts that I entered onto a TableViewCell
The issue?
When submitting a blank text field, there is no prompt to tell the user to enter texts. It will just display the image only in the UITableViewCell without any texts which is the obvious result when inputting no strings.
What do I want to achieve?
To add a validation message to the user when they enter no text and cancel the process of submitting to the database.
What I've already tried?
Please see below for code. Note: Submitting an image and texts to the database already works, my issue is with textfield validation
func createBrandItem (with image:UIImage){
let brandItem = Brand(context: managedObjectContext)
brandItem.image = NSData(data: image.jpegData(compressionQuality: 0.3)!) as Data
let inputAlert = UIAlertController(title: "New Brand", message: "Enter an item and a brand.", preferredStyle: .alert)
inputAlert.addTextField { (textfield:UITextField) in
textfield.placeholder = "Item"
}
inputAlert.addTextField { (textfield:UITextField) in
textfield.placeholder = "Brand"
}
inputAlert.addAction(UIAlertAction(title: "Save", style: .default, handler: { (action:UIAlertAction) in
let itemTextField = inputAlert.textFields?.first
let productTextField = inputAlert.textFields?.last
if (itemTextField?.text!.isEmpty)! || (productTextField?.text!.isEmpty)! {
let alertBlankInput = UIAlertController(title: "Blank Input", message: "Please don't leave the textfields empty.", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.cancel)
alertBlankInput.addAction(okAction)
self.present(alertBlankInput, animated: true, completion: nil)
}
if itemTextField?.text != "" && productTextField?.text != "" {
brandItem.item = itemTextField?.text
brandItem.brand = productTextField?.text
do{
try self.managedObjectContext.save()
self.loadData()
}
catch{
print("Could not save data \(error.localizedDescription)")
}
}
}))
inputAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
if self.presentedViewController == nil {
self.present(inputAlert, animated: true, completion: nil)
}
else {
self.dismiss(animated: false, completion: nil)
self.present(inputAlert, animated: true, completion: nil)
}
}
Breaking the code down
The snippet below is what I tried adding to the createBrandItem function. When I add this ifstatement when debugging the app, it uses this conditional statement but also completes the conditional statement where it adds to the database.
if (itemTextField?.text!.isEmpty)! || (productTextField?.text!.isEmpty)! {
let alertBlankInput = UIAlertController(title: "Blank Input", message: "Please don't leave the textfields empty.", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.cancel)
alertBlankInput.addAction(okAction)
self.present(alertBlankInput, animated: true, completion: nil)
}
The code below is the snippet which adds the data to the database.
let brandItem = Brand(context: managedObjectContext)
brandItem.image = NSData(data: image.jpegData(compressionQuality: 0.3)!) as Data
if itemTextField?.text != "" && productTextField?.text != "" {
brandItem.item = itemTextField?.text
brandItem.brand = productTextField?.text
do{
try self.managedObjectContext.save()
self.loadData()
}
catch{
print("Could not save data \(error.localizedDescription)")
}
}
Summary
How would I go if the textfields are empty and stops the process of submitting to the database?
I'd recommend creating your own child view controller which you present as a model dialogue, which would be more elegant and give you far greater control over the process flow. However if you want to continue with an alert controller, you could make it work by creating the OK action in a disabled state:
let okAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.cancel)
okActions.isEnabled = false
inputAlert.addAction(okAction)
and then use textFields' delegates to validate the content of the text fields (you'd need to have a means to coordinate between the two textfields - you could use the alertController's textfields? array to cross-check, or use local variables as flags) and when you're happy with the content of both
inputAlert.actions.filter({$0.title == "OK" }).first?.isEnabled = true
I am currently trying to create a class that will simplify the process of defining an UIAlert.
As the traditional way of initializing an alert is
let alert = UIAlertController(title:"hello world", message: "how are you",preferedStyle: .actionSheet)
let ok = UIAlertAction(title:"ok",style:.default,handler: {(action) -> Void in print("ok")})
alert.addAction(ok)
self.presentViewController(alert,animated:true,completion:nil)
However, as i am going to be having the same format of alert in alot of places through out my app, I was thinking of making a single class that contains my entire alert object with all actions added so i can simply do:
let alert = MyAlert()
self.presentViewController(alert,animated:true,completion:nil)
I have
class myAlert: UIAlertController{
init() {
super.init(title:"hello world", message: "how are you", preferredStyle: .actionSheet)
}
}
But I seem to be getting an error "Must call a designated initliazer of the superclass 'UIAlertController'
Can you explain to me what I am doing wrong and send me in the right direction. I am fairly new to swift so any help is much appreciated. Thank you
You could just create an extension and whenever you want to display a UIAlertController, just call the method. With an extension, it can be used throughout your app.
extension UIViewController {
func displayMessageAlert(withAlertTitle alertTitle: String, andMessage message: String) {
let alert = UIAlertController(title: alertTitle, message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .destructive, handler: nil)
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
}
Usage on a UIViewController:
self.displayMessageAlert(withAlertTitle: "Your Title", andMessage: "Display your message here.")
Edit: Thank you for the replies, yes I needed an alert and not an action sheet! I have implemented this new code and it works, but is there a reason why it segues to the next view before the user can enter a ride title?
Also it throws this message in the debug console, should I be concerned?
2016-02-16 12:30:21.675 CartoBike[687:128666] Presenting view controllers on detached view controllers is discouraged .
#IBAction func stopAction(sender: UIButton) {
let alert = UIAlertController(title: "Ride Stopped", message: "Give a title to your ride", preferredStyle: .Alert)
let saveAction = UIAlertAction(title: "Save", style: .Default,
handler: { (action:UIAlertAction) -> Void in
// Allow for text to be added and appended into the RideTableViewController
let textField = alert.textFields!.first
rideContent.append(textField!.text!)
})
let cancelAction = UIAlertAction(title: "Cancel",
style: .Default) { (action: UIAlertAction) -> Void in
}
alert.addTextFieldWithConfigurationHandler {
(textField: UITextField) -> Void in
}
alert.addAction(saveAction)
alert.addAction(cancelAction)
// Save the ride
saveRide()
// Automatically segue to the Ride details page
self.performSegueWithIdentifier("ShowRideDetail", sender: nil)
presentViewController(alert, animated: true, completion: nil)
timer.invalidate()
self.stopLocation()
}
Overview:
I am working my way through my first real app. The basic logic is a home screen to start a new ride or view previous rides. Starting a ride will open a new view with a map to record a bicycle ride based on the users location, this can be stopped and save the ride and immediately switch to a new view to see a map of the ride. Alternatively from the home screen the user can select previous rides and view a list of their old rides in a table view and select one and transition to a detailed view with a map of their ride.
Problem: When adding a UIAlertAction I would like there to be a save and cancel feature. In addition, I would like the user to be able to add a custom title by typing it in via a text field. The input from the text field will be appended to a global variable called rideContent that is tied to the creation of new cells in the table view to store multiple bike rides by unique title.
Research:
I have reviewed the questions titled "How to add a TextField to UIAlertView in Swift" & "Writing handler for UIAlertAction" and still can't seem to discern what I have done wrong. Ideally the alert within this screenshot of an app from the raywenderlich site is what I would like it to look like. I am not sure if what I am trying to do is even possible since there a so many view controllers involved. Granted I am new to swift and I'm sure I am missing something obvious!
Currently getting an error of
"Type of expression is ambiguous without more context", see screenshot: here
Here is the UIAlertController code:
// The timer pauses & the location stops updating when the the stop button is pressed
#IBAction func stopAction(sender: UIButton) {
var inputTextField: UITextField?
let actionSheetController = UIAlertController (title: "Ride Stopped", message: "Add a title to your ride", preferredStyle: UIAlertControllerStyle.ActionSheet)
// Add a cancel action
actionSheetController.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil))
// Add a save action
actionSheetController.addAction(UIAlertAction(title: "Save", style: UIAlertActionStyle.Default, handler: {
(actionSheetController) -> Void in
//Add a text field --- Getting an error on this line
actionSheetController.addTextFieldWithConfigurationHandler { textField -> Void in
// you can use this text field
inputTextField = textField
// Append the ride title to the table view
rideContent.append(textField!.text!)
// Update when user saves a new ride to store the ride in the table view for permanent storage
NSUserDefaults.standardUserDefaults().setObject(rideContent, forKey: "rideContent")
// Save the ride
self.saveRide()
// Automatically segue to the Ride details page
self.performSegueWithIdentifier("ShowRideDetail", sender: nil)
}}))
//present actionSheetController
presentViewController(actionSheetController, animated: true, completion: nil)
timer.invalidate()
self.stopLocation()
}
Thank you stack overflow for any help you may offer!
You're using an .ActionSheet while the tutorial you showed is using an .Alert
Action Sheets can have buttons but not text fields.
"Alerts can have both buttons and text fields, while action sheets only support buttons."
NSHipster
Use your tableview Array when Add Button is pressed and "Add alertview textfirld to tableview array and then reload tableview".
#IBAction func btnAdd(_ sender: Any) {
let alertController = UIAlertController(title: "Add Category", message: "", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Add", style: .default, handler: { alert -> Void in
let firstTextField = alertController.textFields![0] as UITextField
self.categories.add(firstTextField.text!)
self.tblCatetgory.reloadData()
})
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: {
(action : UIAlertAction!) -> Void in })
alertController.addTextField { (textField : UITextField!) -> Void in
textField.placeholder = "Enter Category!!!"
}
alertController.addAction(saveAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
}
Couldn't find a clear and informative explanation for this.
After searching a while on a subject I didn't
find a clear explanation , even in it's class reference
UIAlertController Reference
It is ok, but not clear enough for me.
So after collecting some peaces I decided to make my own explanation
(Hope it helps)
So here it goes:
UIAlertView is deprecated as pointed out :
UIAlertView in Swift
UIAlertController should be used in iOS8+
so to create one first we need to instantiate it,
the Constructor(init) gets 3 parameters:
2.1 title:String -> big-bold text to display on the top of alert's dialog box
2.2 message:String -> smaller text (pretty much explains it's self)
2.3 prefferedStyle:UIAlertControllerStyle -> define the dialog box style, in most cases: UIAlertControllerStyle.Alert
Now to actually show it to the user, we can use showViewController or presentViewController and pass our alert as parameter
To add some interaction with a user we can use:
4.1
UIAlertController.addAction to create buttons
4.2
UIAlertController.addTextField to create text fields
Edit note: code examples below, updated for swift 3 syntax
Example 1: Simple Dialog
#IBAction func alert1(sender: UIButton) {
//simple alert dialog
let alert=UIAlertController(title: "Alert 1", message: "One has won", preferredStyle: UIAlertControllerStyle.alert);
//show it
show(alert, sender: self);
}
Example 2: Dialog with one input textField & two buttons
#IBAction func alert2(sender: UIButton) {
//Dialog with one input textField & two buttons
let alert=UIAlertController(title: "Alert 2", message: "Two will win too", preferredStyle: UIAlertControllerStyle.alert);
//default input textField (no configuration...)
alert.addTextField(configurationHandler: nil);
//no event handler (just close dialog box)
alert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.cancel, handler: nil));
//event handler with closure
alert.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.default, handler: {(action:UIAlertAction) in
let fields = alert.textFields!;
print("Yes we can: "+fields[0].text!);
}));
present(alert, animated: true, completion: nil);
}
Example 3: One customized input textField & one button
#IBAction func alert3(sender: UIButton) {
// one input & one button
let alert=UIAlertController(title: "Alert 3", message: "Three will set me free", preferredStyle: UIAlertControllerStyle.alert);
//configured input textField
var field:UITextField?;// operator ? because it's been initialized later
alert.addTextField(configurationHandler:{(input:UITextField)in
input.placeholder="I am displayed, when there is no value ;-)";
input.clearButtonMode=UITextFieldViewMode.whileEditing;
field=input;//assign to outside variable(for later reference)
});
//alert3 yesHandler -> defined in the same scope with alert, and passed as event handler later
func yesHandler(actionTarget: UIAlertAction){
print("YES -> !!");
//print text from 'field' which refer to relevant input now
print(field!.text!);//operator ! because it's Optional here
}
//event handler with predefined function
alert.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.default, handler: yesHandler));
present(alert, animated: true, completion: nil);
}
Hope It helps, and good luck ;-)
An instance of the UIAlertController can be presented modally on screen just as any other UIViewController using the presentViewController:animated:completion: method. What makes the UIAlertController instance differentiate between working as an ActionSheet or as an AlertView is the style parameter you pass when creating it.
No more delegation
If you have used a UIActionSheet or UIAlertView, you know that the way to get a callback from it is for a class (in most cases the ViewController) to implement the UIActionSheetDelegate or UIAlertViewDelegate protocol. There are some open source projects that replaced this delegation pattern with block based callbacks, but the official APIs were never updated. UIAlertController does not use delegation. Instead, it has a collection of UIAlertAction items, that use closures (or blocks if you are using Objective-C) to handle user input.
For Action Sheet
#IBAction func showActionSheet(sender: AnyObject) {
//Create the AlertController
let actionSheetController: UIAlertController = UIAlertController(title: "Action Sheet", message: "Swiftly Now! Choose an option!", preferredStyle: .ActionSheet)
//Create and add the Cancel action
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//Just dismiss the action sheet
}
actionSheetController.addAction(cancelAction)
//Create and add first option action
let takePictureAction: UIAlertAction = UIAlertAction(title: "Take Picture", style: .Default) { action -> Void in
//Code for launching the camera goes here
}
actionSheetController.addAction(takePictureAction)
//Create and add a second option action
let choosePictureAction: UIAlertAction = UIAlertAction(title: "Choose From Camera Roll", style: .Default) { action -> Void in
//Code for picking from camera roll goes here
}
actionSheetController.addAction(choosePictureAction)
//Present the AlertController
self.presentViewController(actionSheetController, animated: true, completion: nil)
}
For AlertView with Text Field
#IBAction func showAlert(sender: AnyObject) {
//Create the AlertController
let actionSheetController: UIAlertController = UIAlertController(title: "Alert", message: "Swiftly Now! Choose an option!", preferredStyle: .Alert)
//Create and add the Cancel action
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//Do some stuff
}
actionSheetController.addAction(cancelAction)
//Create and an option action
let nextAction: UIAlertAction = UIAlertAction(title: "Next", style: .Default) { action -> Void in
//Do some other stuff
}
actionSheetController.addAction(nextAction)
//Add a text field
actionSheetController.addTextFieldWithConfigurationHandler { textField -> Void in
//TextField configuration
textField.textColor = UIColor.blueColor()
}
//Present the AlertController
self.presentViewController(actionSheetController, animated: true, completion: nil)
}
Some of the syntax has changed since the original responses. Here is some sample code that alerts the user if they are not signed in to iCloud.
CKContainer.default().accountStatus { (accountStatus, error) in
switch accountStatus {
case .available:
print("iCloud Available")
case .noAccount:
print("No iCloud account")
//simple alert dialog
let alert=UIAlertController(title: "Sign in to iCloud", message: "This application requires iClound. Sign in to your iCloud account to write records. On the Home screen, launch Settings, tap iCloud, and enter your Apple ID. Turn iCloud Drive on. If you don't have an iCloud account, tap Create a new Apple ID", preferredStyle: UIAlertControllerStyle.alert);
//no event handler (just close dialog box)
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: nil));
//show it
self.present(alert, animated: true, completion: nil)
case .restricted:
print("iCloud restricted")
case .couldNotDetermine:
print("Unable to determine iCloud status")
}
}