Send a message and dismiss keyboard at same time in Swift - ios

Blue up Arrow button is for sending a message
I am using the following method to present and dismiss keyboard and shift my text field with keyboard
NotificationCenter.default.addObserver(self, selector:#selector(self.keyboardNotification(notification:)),name: UIResponder.keyboardWillChangeFrameNotification,object: nil)
#objc func keyboardNotification(notification: NSNotification) {
if let userInfo = notification.userInfo {
let endFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let endFrameY = endFrame?.origin.y ?? 0
let duration:TimeInterval = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue
let animationCurve:UIView.AnimationOptions = UIView.AnimationOptions(rawValue: animationCurveRaw)
self.view1.frame.origin.y = endFrameY - self.view1.frame.height
UIView.animate(withDuration: duration,
delay: TimeInterval(0),
options: animationCurve,
animations: { self.view.layoutIfNeeded() },
completion: nil)
}
}
The problem I am facing is that I can't send the message and dismiss the keyboard at same time .I have a send button in the view which when pressed only dismisses the keyboard and doesn't send the message.
How can I send message as well as dismiss the keyboard at once?
EDIT:
This is my upload button action
#IBAction func uploadBtn(_ sender: UIButton)
{
if photoArray.count > 0 || self.audioRecorded == true || enteredTF.text != ""{
CustomAlert.showWait()
var i : Int = 0
let baseUrl = config.baseURL
parameters = [
"SKEY":self.skey,
"FID":"NEWMESSAGE",
"HKEY":"5673126578",
"LONGITUDE" : longitude as? String ?? "",
"LATITUDE" : latitude as? String ?? "",
"IPADDRESS" : "\(getIPAddress())",
"MESSAGE" : enteredTF.text!,
"USERID" : Uname,
"PHONEID" : phoneID,
"TIMEZONE" : getCurrentTimeZone()]
print(parameters)
Alamofire.upload(multipartFormData: { (multipartFormData) in
for (key, value) in self.parameters {
multipartFormData.append("\(value)".data(using: String.Encoding.utf8)!, withName: "\(key)" )
}
for (image) in self.photoArray {
// print(i)
if let imageData = image.jpegData(compressionQuality: 0.5) {
OperationQueue.main.addOperation {
multipartFormData.append(imageData, withName: "files", fileName: "image\(i).jpeg", mimeType: "image/jpeg")
i=i+1
}
}
}
if self.audioRecorded == true
{
multipartFormData.append(self.getFileUrl(), withName: "myRecording.m4a")
}
else
{
multipartFormData.append("".data(using: String.Encoding.utf8)!, withName: "Voice",mimeType: "m4a")
//i = i+1
}
}, usingThreshold: UInt64.init(), to: baseUrl, method: .post) { (result) in
switch result{
case .success(let upload, _, _):
upload.responseString { response in
print("Succesfully uploaded = \(response)")
CustomAlert.hideWait()
let msg = SCLAlertView()
msg.showSuccess("", subTitle: "Your message has been successfully sent")
self.enteredTF.text = ""
OperationQueue.main.addOperation {
self.viewAllMessages()
self.photoArray.removeAll()
self.imageCollectionV.reloadData()
}
if let err = response.error{
print(err)
return
}
}
case .failure(let error):
print("Error in upload: \(error.localizedDescription)")
}
}
// }
}
}

Instead of using UIResponder.keyboardWillChangeFrameNotification, you can use
UIResponder.keyboardWillShowNotification as observer when keyboard will show and
UIResponder.keyboardWillHideNotification as observer when keyboard will dismissed / hide.
On UIResponder.keyboardWillHideNotification's handler you can add your code to configure UIView and call your method to send message
Here's some example code :
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(showKeyboard(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(hideKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func showKeyboard(_ notification:NSNotification) {
if let userInfo = notification.userInfo {
//TODO: Do your UI Stuff
}
}
#objc func hideKeyboard() {
DispatchQueue.main.async {
//TODO: Do your UI Stuff
}
//TODO: Send Message
}

Related

Buggy UI Tableview

I want the view to be like Apple maps UI Tableview when you click on a map annotation, the UI Tableview moves up with the loaded information and it is smooth. I created a UITableView to load data from a Yelp API and Firebase database. While I do have the data loaded in the UI Tableview, there seems to be choppiness in the movement of the tableview. For example, when I first click on a map annotation, the UI Tableview will pop up, but in a random position and then after the Yelp API loads, it will move again to its default position. Another thing is that if I use a swipe gesture before the Yelp API loads, the UI Tableview will move accordingly, but then reset to its original position when the Yelp API data loads, which then I have to redo the swipe gesture.
There are many parts of this tableview, so I will provide a list of pieces of code that I use:
Note: The UI Tableview (locationInfoViews) is configured in the ViewDidLoad
Swiping up/down
func configureGestureRecognizer() {
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeGesture))
swipeUp.direction = .up
addGestureRecognizer(swipeUp)
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeGesture))
swipeDown.direction = .down
addGestureRecognizer(swipeDown)
}
func animateInputView(targetPosition: CGFloat, completion: #escaping(Bool) -> ()) {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {self.frame.origin.y = targetPosition}, completion: completion)
}
// MARK: - Handlers
#objc func handleSwipeGesture(sender: UISwipeGestureRecognizer) {
if sender.direction == .up {
if expansionState == .Disappeared {
animateInputView(targetPosition: self.frame.origin.y - 100) { (_) in
self.expansionState = .NotExpanded
}
}
if expansionState == .NotExpanded {
animateInputView(targetPosition: self.frame.origin.y - 200) { (_) in
self.expansionState = .PartiallyExpanded
}
}
if expansionState == .PartiallyExpanded {
animateInputView(targetPosition: self.frame.origin.y - 250) { (_) in
self.expansionState = .FullyExpanded
}
}
} else {
if expansionState == .FullyExpanded {
animateInputView(targetPosition: self.frame.origin.y + 250) { (_) in
self.expansionState = .PartiallyExpanded
}
}
if expansionState == .PartiallyExpanded {
animateInputView(targetPosition: self.frame.origin.y + 200) { (_) in
self.expansionState = .NotExpanded
}
}
if expansionState == .NotExpanded {
animateInputView(targetPosition: self.frame.origin.y + 100) { (_) in
self.expansionState = .Disappeared
}
}
}
}
When select annotation, information from Yelp and Firebase will be loaded into the location info view and the animation should move up
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
self.locationPosts.removeAll()
self.inLocationInfoMode = true
if view.annotation is MKUserLocation {
print("selected self")
} else {
self.selectedAnnotation = view.annotation as? Annotation
let coordinates = selectedAnnotation.coordinate
let coordinateRegion = MKCoordinateRegion(center: coordinates, latitudinalMeters: 1000, longitudinalMeters: 100)
mapView.setRegion(coordinateRegion, animated: true)
self.locationInfoViews.locationTitle = self.selectedAnnotation.title
// Fetch Location Post
guard let currentUid = Auth.auth().currentUser?.uid else {return}
LOCATION_REF.child(self.selectedAnnotation.title).child(currentUid).observe(.childAdded) { (snapshot) in
let postId = snapshot.key
Database.fetchLocationPost(with: postId) { (locationPost) in
self.locationPosts.append(locationPost)
self.locationPosts.sort { (post1, post2) -> Bool in
return post1.creationDate > post2.creationDate
}
self.locationInfoViews.locationResults = self.locationPosts
// self.locationInfoViews.tableView.reloadData()
// Fetch Location Information
guard let locationName = locationPost.locationName else {return}
guard let locationAddress = locationPost.address else {return}
let locationRef = COORDINATES_REF.child(locationName).child(locationAddress)
locationRef.child("mainTypeOfPlace").observe(.value) { (snapshot) in
guard let mainTypeOfPlace = snapshot.value as? String else {return}
// self.locationInfoViews.typeOfPlace = mainTypeOfPlace
locationRef.child("shortAddress").observe(.value) { (snapshot) in
guard let address1 = snapshot.value as? String else {return}
locationRef.child("city").observe(.value) { (snapshot) in
guard let city = snapshot.value as? String else {return}
locationRef.child("state").observe(.value) { (snapshot) in
guard let state = snapshot.value as? String else {return}
locationRef.child("countryCode").observe(.value) { (snapshot) in
guard let country = snapshot.value as? String else {return}
// fetch Yelp API Data
self.service.request(.match(name: locationName, address1: address1, city: city, state: state, country: country)) {
(result) in
switch result {
case .success(let response):
let businessesResponse = try? JSONDecoder().decode(BusinessesResponse.self, from: response.data)
let firstID = businessesResponse?.businesses.first?.id
self.information.request(.BusinessID(id: firstID ?? "")) {
(result) in
switch result {
case .success(let response):
if let jsonResponse = try? JSONSerialization.jsonObject(with: response.data, options: []) as? [String: Any] {
// print(jsonResponse)
if let categories = jsonResponse["categories"] as? Array<Dictionary<String, AnyObject>> {
var mainCategory = ""
for category in categories {
mainCategory = category["title"] as? String ?? ""
break
}
self.locationInfoViews.typeOfPlace = mainCategory
}
let price = jsonResponse["price"] as? String ?? ""
if let hours = jsonResponse["hours"] as? Array<Dictionary<String, AnyObject>> {
for hour in hours {
let isOpen = hour["is_open_now"] as? Int ?? 0
if isOpen == 1 {
let attributedText = NSMutableAttributedString(string: "open ", attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor(rgb: 0x066C19)])
attributedText.append(NSAttributedString(string: " \(price)", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.black]))
self.locationInfoViews.hoursLabel.attributedText = attributedText
} else {
let attributedText = NSMutableAttributedString(string: "closed ", attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.red])
attributedText.append(NSAttributedString(string: " \(price)", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.darkGray]))
self.locationInfoViews.hoursLabel.attributedText = attributedText
}
}
}
}
case .failure(let error):
print("Error: \(error)")
}
}
case .failure(let error):
print("Error: \(error)")
}
}
}
}
}
}
}
self.locationInfoViews.tableView.reloadData()
}
}
// enable the goButton
if inLocationInfoMode {
locationInfoViews.goButton.isEnabled = true
locationInfoViews.coordinates = selectedAnnotation.coordinate
}
// the movement of the location info view
if self.locationInfoViews.expansionState == .Disappeared {
self.locationInfoViews.animateInputView(targetPosition: self.locationInfoViews.frame.origin.y - 335) { (_) in
self.locationInfoViews.expansionState = .PartiallyExpanded
}
}
}
}
Try adding a completion block for the fetching, first extract it into a separate function. On tap on the annotation show some loading state, while you load the data. Once you receive the completion block, decide what to do depending on the result. The fetching will be done while the user sees a loading state, and no flickering will occur.

Displaying Viewcontroller based on Notification payload swift

I am getting a push notification and displaying a viewcontroller based on the content of the notification.When the notification is triggered manually from postman, I get the following userInfo
[AnyHashable("google.c.a.e"): 1, AnyHashable("gcm.notification.type"): new_property, AnyHashable("gcm.notification.meta"): {"property_id":"3"}, AnyHashable("gcm.message_id"): 1570614460795417, AnyHashable("aps"): {
alert = {
body = "A new property has been published is in your area";
title = "New Property";
};
}]
// CASTING AS [String: Any]
["google.c.a.e": 1, "gcm.notification.meta": {"property_id":"3"}, "gcm.notification.type": new_property, "aps": {
alert = {
body = "A new property has been published is in your area";
title = "New Property";
};
}, "gcm.message_id": 1570614460795417]
This in turn works and displays the required screen. But when ever a user action triggers the same notification, I get this userInfo
[AnyHashable("gcm.notification.meta"): {"property_id":46}, AnyHashable("gcm.notification.type"): new_property, AnyHashable("body"): A new property has just been listed in your area by Ebenezer Kilani, AnyHashable("title"): New property Listing, AnyHashable("type"): new_property, AnyHashable("meta"): {"property_id":46}, AnyHashable("aps"): {
alert = {
body = "A new property has just been listed in your area by Ebenezer Kilani";
title = "New property Listing";
};
}, AnyHashable("gcm.message_id"): 1570550088749025, AnyHashable("google.c.a.e"): 1]
// CASTING AS [String: Any]
["meta": {"property_id":46}, "type": new_property, "title": New property Listing, "gcm.message_id": 1570550088749025, "gcm.notification.meta": {"property_id":46}, "body": A new property has just been listed in your area by Ebenezer Kilani, "aps": {
alert = {
body = "A new property has just been listed in your area by Ebenezer Kilani";
title = "New property Listing";
};
}, "google.c.a.e": 1, "gcm.notification.type": new_property]
the second payload just opens the application but never goes into the view controller I need it to. That is, it opens the home page only.
How I am getting the userInfo
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let usrInfo = userInfo as? [String: Any] {
extractUserInfo(userInfo: usrInfo, completion: completionHandler)
}
completionHandler()
}
func extractUserInfo(userInfo: [String: Any], completion: #escaping () -> Void) {
if let type = userInfo["gcm.notification.type"] as? String {
Storage.instance.savePush(type)
if type == "new_property" {
if let meta = userInfo["gcm.notification.meta"] as? String {
if let data = meta.data(using: .utf8) {
do {
if let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] {
guard let propertyId = json["property_id"] as? String, let id = Int(propertyId) else {return}
NotificationEvent.isFromNotification.accept(true)
NotificationEvent.id.accept(id)
} else {
print("JSON is invalid")
}
} catch {
print("Exception converting: \(error)")
}
}
}
}
}
}
how I handle the push to display required Viewcontroller
class Remote {
var navigationController: UINavigationController?
let disposeBag = DisposeBag()
#discardableResult
init(navigationController: UINavigationController?) {
self.navigationController = navigationController
rxEvent()
}
func rxEvent() {
NotificationEvent.isFromNotification.asObservable().subscribe(onNext: { bool in
print("Exception converting: bool \(bool)")
if bool {
NotificationEvent.id.asObservable()
.delay(1, scheduler: MainScheduler.instance)
.subscribe(onNext: { int in
if int > 0 {
if Storage.instance.getPush() == "new_property" {
self.presentPropertyDetailVC(int)
} else if Storage.instance.getPush() == "new_rating" || Storage.instance.getPush() == "coverage_area" {
print(int)
}
}
}).disposed(by: self.disposeBag)
NotificationEvent.isFromNotification.accept(false)
}
}).disposed(by: disposeBag)
}
func presentPropertyDetailVC(_ uid: Int) {
// navigationVC.modalPresentationStyle = .formSheet
let controller = PropertyDetailVC()
controller.propertyId = uid
print("Exception converting: 11 \(controller)")
navigationController?.pushViewController(controller, animated: true)
}
}
then I instantiate Remote in my home VC
Any reason why the first payload work and the second does not and anything I could do to correct that.
This line
guard let propertyId = json["property_id"] as? String, let id = Int(propertyId) else {return}
is getting to the "return" because on the second meta 46 is an int not a string (note that it have not quotes. To fix it you can try
guard let propertyId = json["property_id"] as? String ?? String(json["property_id"] as? Int ?? 0), let id = Int(propertyId) else {return}

How to save two items which comes with pushNotification

1)When notification comes,i need to save data from AnyHashable("data") "amount" and "inBalance"?
2)From server comes two pushes first push with "message",second "nil message body".There when nil message comes i need to silent it?
How i can save it?
I'm using xCode 10.2.1
my code for pushNotification.swift looks like:
enum PillikanRemoteNotification:Int {
case empty
case notification = 2
case news = 3
}
class PushManagerNotification {
func convert(with value: [AnyHashable: Any], and state: PushNotificationAction) {
guard let json = value as? [String: AnyObject], !json.isEmpty else { return }
guard let jsonString = value["data"] as? String, !jsonString.isEmpty else { return }
guard let jsonData = jsonString.data(using: .utf8) else { return }
guard let rawDictionary = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableLeaves) else { return }
guard let dictionary = rawDictionary as? [String: AnyObject] else { return }
guard let actionType = dictionary["action"] as? String, let action = Int(actionType) else { return }
guard let notificationType = PillikanRemoteNotification(rawValue: action) else { return }
guard let messageBody = dictionary["msg"] as? [String: AnyObject] else { return }
if state == .show {
showBadges(dictionary: messageBody)
return
}
switch notificationType {
case .notification:
showNotification(dictionary: messageBody)
break
default:
break;
}
}
private func showBadges(dictionary: [String: AnyObject]) {
guard let badgeMessage = dictionary["badges"] as? [String: AnyObject] else { return }
guard let badges = Badges(JSON: badgeMessage) else { return }
UIApplication.shared.notificationBadgesForNotification = badges.notifications
UIApplication.shared.notificationBadgesForNews = badges.news
UIApplication.shared.applicationIconBadgeNumber = badges.news + badges.notifications
NotificationCenter.default.post(name: .badges, object: badges)
}
private func showNotification(dictionary: [String:AnyObject]) {
if let message = NotificationEntity(JSON: dictionary) {
NotificationCenter.default.post(name: .notification, object: message);
}
}
extension Notification.Name {
static let empty = Notification.Name("empty");
static let notification = Notification.Name("notification");
static let news = Notification.Name("news");
static let badges = Notification.Name("badges")
}
For Silent Notifications there are two criterions:
1.) The payload's aps dictionary must include the content-available key with a value of 1.
2.) The payload's aps dictionary must not contain the alert, sound, or badge keys.
example
{
"aps" : {
"content-available" : 1
},
"cusomtkey1" : "bar",
"cusomtkey2" : 42
}
inside the didReceive function use this :
let amount = userInfo["data"]["amount"]
let inBalance = userInfo["data"]["inBalance"]
I'm not sure if this is gonna work or not I didn't try it.. try this function for the notification properties:
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
let message = userInfo["message"] as! String
if message.isEmpty == true {
completionHandler([.alert, .badge])
}else {
completionHandler([.alert, .badge, .sound])
}
}
Here you can see, How I‘m taking token from AnyHashable and then take a specific value from it.
Try this code. It will help you out.
if(response.result.isSuccess){
let data = response.result.value
print(data!)
if (data?.contains("access_token"))!{
var dictonary:NSDictionary?
if let data = data!.data(using: String.Encoding.utf8) {
do {
dictonary = try JSONSerialization.jsonObject(with: data, options: []) as? [String:AnyObject] as NSDictionary?
UserDefaults.standard.set(dictonary!["access_token"]!, forKey: "token")
}catch let error as NSError {
print(error)
}
}
}
}

Upload image to my server via PHP using Swift

I am using the code below to upload image to my server, the code send the request but the response always from the API is (there was an error).
In the same time of uploading the image also some information of this image will be stored in mySql.
Here is my code, I am using Xcode 10.1 and Swift 4.2
#IBAction func uploadImage(_ sender: Any) {
self.showActivityIndicator()
//Post URL
let url = "https://website.com/folder/include/upload.php"
//Getting text from textFiled!
let name = nameField.text!
let age = ageField.text!
//Call Parameters
let params: Parameters = ["name": name,"age": age]
//Checking image place holder
let image = UIImage(named: "map.png")
//Checking if empty name or age fileds
if name.isEmpty || age.isEmpty{
self.hideActivityIndicator()
myAlert(title: "Error", msg: "Make sure you enter all the required information!")
}
//Checking if image is not selected!!
else if imageView.image == image
{
self.hideActivityIndicator()
myAlert(title: "Error", msg: "Make sure you choose an image!")
}else{
let imageToUpload = self.imageView.image!
Alamofire.upload(multipartFormData:
{
(multipartFormData) in
multipartFormData.append(imageToUpload.jpegData(compressionQuality: 0.75)!, withName: "image", fileName: self.generateBoundaryString(), mimeType: "image/jpeg")
for (key, value) in params
{
multipartFormData.append((value as AnyObject).data(using: String.Encoding.utf8.rawValue)!, withName: key)
}
}, to:url,headers:nil)
{ (result) in
switch result {
case .success(let upload,_,_ ):
upload.uploadProgress(closure: { (progress) in
//Print progress
self.showActivityIndicator()
})
upload.responseJSON
{ response in
//print response.result
if let result = response.result.value {
//Calling response from API
let message = (result as AnyObject).value(forKey: "message") as! String
let status = (result as AnyObject).value(forKey: "status") as! String
//Case Success
if status == "1" {
self.hideActivityIndicator()
print("Your Results are ====> ",result)
self.myAlert(title: "Data Upload", msg: message)
self.imageView.image = UIImage(named: "map.png")
self.nameField.text = ""
self.ageField.text = ""
}else{
self.hideActivityIndicator()
self.myAlert(title: "Error Uploading", msg: message)
}
}
}
case .failure(let encodingError):
print(encodingError)
break
}
}
}
}
}
Here is the PHP file code:
<?php
include 'include/connect.php';
//Get Param Data
$name = $_POST["name"];
$age = $_POST["age"];
$xName = mysqli_real_escape_string($conn, $name);
$xAge = mysqli_real_escape_string($conn, $age);
//Results Array
$result = array();
//Image setup
$uploads_dir = 'img';
$tmp_name = $_FILES["image"]["tmp_name"];
$image_name = basename($_FILES["image"]["name"]);
$supported_image = array('gif','jpg','jpeg','png');
$ext = strtolower(pathinfo($image_name, PATHINFO_EXTENSION));
if(empty($xName) || empty($xAge)|| empty($image_name))
{
// Send some dummy result back to the iOS app
$result["message"] = "Sorry, there was an error uploading your file.";
$result["status"] = "0";
$result["post"] = $_POST;
$result["files"] = $_FILES;
}
if (! in_array($ext, $supported_image))
{
// Send some dummy result back to the iOS app
$result["message"] = "Sorry, Image extension is not Allowed!";
$result["status"] = "0";
$result["post"] = $_POST;
$result["files"] = $_FILES;
}
else
{
$query ="INSERT INTO images (name, age, image) VALUES ('$xName', '$xAge','$image_name')";
if (mysqli_query($conn, $query)) {
move_uploaded_file($tmp_name,"$uploads_dir/$image_name");
// Send some dummy result back to the iOS app
$result["message"] = "Data has been uploaded successfully.";
$result["status"] = "1";
$result["post"] = $_POST;
$result["files"] = $_FILES;
}
}
echo json_encode($result);
?>
The response from the API seems to be missing of some information, but I am filling the two fields with informations needed, which are the (name and age).
I do not what I am missing to complete uploading the image and its informations.
Thanks
try this in your button click event--->
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let addPhotos = UIAlertAction(title: "Choose Photo", style: .default) { (addPhoto) in
self.imgPicker.sourceType = .photoLibrary
self.imgPicker.allowsEditing = false
self.present(self.imgPicker, animated: true, completion: nil)
}
let camera = UIAlertAction(title: "Camera Photo", style: .default) { (CameraPhoto) in
self.imgPicker.sourceType = .camera
self.imgPicker.allowsEditing = false
self.present(self.imgPicker, animated: true, completion: nil)
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel) { (Cencel) in
self.dismiss(animated: true, completion: nil)
}
alert.addAction(addPhotos)
alert.addAction(camera)
alert.addAction(cancel)
self.present(alert, animated: true, completion: nil)
then add this function in your code...
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let img = info[UIImagePickerControllerOriginalImage] as? UIImage{
self.imgProfileImage.image = img
let imgData = UIImageJPEGRepresentation(img, 0.5)!
let parameters = [String : Any]
Alamofire.upload(multipartFormData: { multipartFormData in
multipartFormData.append(imgData, withName: "Folder Name",fileName: "PicName.jpg", mimeType: "image/jpg")
for (key, value) in parameters {
multipartFormData.append((value as AnyObject).data(using: String.Encoding.utf8.rawValue)!, withName: key)
} //Optional for extra parameters
},
to:"Your API is Here.")
{ (result) in
switch result {
case .success(let upload, _, _):
upload.uploadProgress(closure: { (progress) in
print("Upload Progress: \(progress.fractionCompleted)")
})
upload.responseJSON { response in
print(response.result.value)
}
case .failure(let encodingError):
print(encodingError)
}
}
}
self.dismiss(animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.dismiss(animated: true, completion: nil)
}

ios 10+, Swift 3+ - Cannot dismiss UIAlertController from Singleton instance

I have created an overlay to run while I run an async data grab to the server so that users won't continue pressing buttons in the UI until the data grab is done. I have put the function into a global singleton class and I call it while passing in a bool to say whether or not I want to show or hide. I can get it to show but I cannot get it to hide. Here is the code:
class DataModel {
static let sharedInstance = DataModel()
func accessNetworkData(vc: UIViewController, params: [String:Any], wsURLPath: String, completion: #escaping (_ response: AnyObject) -> ()) {
DataModel.sharedInstance.toggleModalProgess(show: true)
// SHOW THE MODAL HERE ONCE THE DATA IS REQUESTED.
let url = URL(string: wsURLPath)!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
do { request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted) } catch let error { print(error.localizedDescription) }
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
DataModel.sharedInstance.toggleModalProgess(show: false)
// NOW SINCE THE NETWORK ACTIVITY IS DONE, HIDE THE UIALERTCONTROLLER
guard error == nil else {
print("WEB SERVICE ERROR <----------------------------<<<<<< " + wsURLPath)
print(error!)
let resp: [String: String] = [ "conn": "failed" ]
DispatchQueue.main.async { completion(resp as NSDictionary) }
return
}
guard let data = data else {
print("WEB SERVICE ERROR <----------------------------<<<<<< " + wsURLPath)
return
}
do {
if let parsedJSON = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
print("WEB SERVICE SUCCESS <----------------------------<<<<<< "+wsURLPath+" "+String(describing:params))
if let parsedResponseDict = parsedJSON["d"] as? NSDictionary {
DispatchQueue.main.async {
completion(parsedResponseDict)
}
}else if let parsedResponseArr = parsedJSON["d"] as? NSArray {
DispatchQueue.main.async {
completion(parsedResponseArr)
}
}else {
print("INVALID KEY <----------------------------<<<<<< " + wsURLPath)
DispatchQueue.main.async {
completion(parsedJSON as AnyObject)
}
}
}
} catch let error {
print("Error with JSON Serialization")
print(error.localizedDescription)
}
})
task.resume()
}
HERE IS WHERE I SET UP THE UIALERTCONTROLLER
let modalAlert = UIAlertController(title: "Please Wait...", message: "Loading Data...", preferredStyle: UIAlertControllerStyle.alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
func toggleModalProgess(show: Bool) -> Void {
print("toggleModalProgess: show = " + String(describing: show))
if (show) {
print("let's turn it on")
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
loadingIndicator.startAnimating()
modalAlert.view.addSubview(loadingIndicator)
modalAlert.show()
}else {
print("let's turn it off")
modalAlert.hide()
}
}
private init() { }
}
NOW THE EXTENSION WHERE THE MAGIC HAPPENS
public extension UIAlertController {
func show() {
let win = UIWindow(frame: UIScreen.main.bounds)
let vc = UIViewController()
vc.view.backgroundColor = .clear
win.rootViewController = vc
win.windowLevel = UIWindowLevelAlert + 1
win.makeKeyAndVisible()
vc.present(self, animated: true, completion: nil)
}
func hide() {
// HERE IS WHERE I NEED TO HIDE IT BUT I AM HAVING ISSUES
}
}
In order to dismiss the UIAlertController (which is a subclass of UIViewController), it should be sufficient to call the dismiss method:
func hide() {
dismiss(animated: true, completion: nil)
}
It works fine in my sample project.
You should do...
self.presentingViewController?.dismiss(animated: true, completion: nil)
Hope it helps

Resources