I'm attempting to perform a segue which passes an image array to a new view controller, but the segue is being performed before the images are added to the array- I believe this is because URLSession, which I'm using to convert a url to an image, takes time.
How would I have the Segue perform after the images are added to the array?
My code where the url is turned into the image:
// When selection is selected
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let overallArray = displayArray[indexPath.section]
// Add Image 1 to imageArrayToBeSent
if let imageUrl = overallArray.fullImage1 {
let url = URL(string: imageUrl)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async {
self.imageArrayToBeSent.append(UIImage(data: data!)!)
}
}.resume()
}
// stringToBeSent = overallArray.fullImage1!
performSegue(withIdentifier: "openNewViewController", sender: self)
}
My code where I call the Segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let info = segue.destination as! NewViewController
info.imageArray = self.imageArrayToBeSent
print(self.imageArrayToBeSent)
}
Do it inside the callback
DispatchQueue.main.async {
self.imageArrayToBeSent.append(UIImage(data: data!)!)
self.performSegue(withIdentifier: "openNewViewController", sender: self)
}
Related
I'm trying to pass a Json response of an Http request from one controller to another, where in the second one I'd like to create a collection view from the recieved data.
import UIKit
class TableViewController: UITableViewController {
let ingredientList = Ingredients().Ingredients
public var arrayDrinks: Array<Any> = []
let session = URLSession.shared
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.reloadData()
tableView.dataSource = self
tableView.delegate = self
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
// MARK: - number of rows
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.ingredientList.count
}
// MARK: - creating cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellaIng", for: indexPath)
let singoloIngrediente = self.ingredientList[indexPath.row]
cell.textLabel?.text = singoloIngrediente
return cell
}
// MARK: - get the Selected Item
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedItem: String = ingredientList[indexPath.row]
print("The selected ingredient is: \(selectedItem)")
// parameter for http request
let param = String(selectedItem.replacingOccurrences(of: " ", with: "_"))
let url = URL(string: "https://www.thecocktaildb.com/api/json/v1/1/filter.php?i=\(param)")!
// MARK: - Http request
let task = session.dataTask(with: url) { data, response, error in
if error != nil || data == nil {
print("Client error!")
return
}
guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
print("Server error!")
return
}
do {
// data from network request
let decoder = JSONDecoder()
let response = try decoder.decode(ObjectDrink.self, from: data!) // ObjectDrink from Model
self.arrayDrinks.append(response.drinks)
let destinationVC = DrinksListCollectionViewController()
destinationVC.remoteArray = response.drinks
print("print array drink \(destinationVC.remoteArray)")
} catch { print(error) }
}
performSegue(withIdentifier: "InglistSegue", sender: self)
task.resume()
// let destinationVC = DrinksListCollectionViewController()
// destinationVC.remoteArray = self.arrayDrinks
// destinationVC.performSegue(withIdentifier: "InglistSegue", sender: self)
} // END didSelectRowAt
}
When I print the response to the console, the array of the second controller is empty, so no data is passing from the first response (array) to the other controller
You need to present it inside the callback of the URLSession.shared.dataTask like
DispatchQueue.main.async {
self.arrayDrinks.append(response.drinks)
let destinationVC = DrinksListCollectionViewController()
destinationVC.remoteArray = response.drinks
print("print array drink \(destinationVC.remoteArray)")
self.present(destinationVC,animated:true,completion:nil)
}
If it's a segue then replace above with ( also inside the completion )
DispatchQueue.main.async {
performSegue(withIdentifier: "InglistSegue", sender: response.drinks)
}
Add this method
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! DrinksListCollectionViewController
destinationVC.remoteArray = sender as! [Model] // model it type of drinks
}
The view controller that you are moving to is not available yet and your line:
let destinationVC = DrinksListCollectionViewController()
Creates a new view controller which is not the one that your app transitions to. To use the view controller that will be shown, you use prepare(for segue: UIStoryboardSegue, sender: Any?) like so:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVC = segue.destination as? DrinksListCollectionViewController {
destinationVC.remoteArray = self.arrayDrinks
}
}
I am new to collection view. I want to retrieve data from CoreData for collection view cell. I know how to retrieve for table view cell but it failed when I use similar way to fetch for collection view. Here are my functions from CoreDataHelper and ViewController class
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
if identifier == "displayCellDetail" {
print("Task View cell tapped")
CollectionViewCoreDataHelper.retrieveTasks()
let indexPath = collectionView.indexPathsForSelectedItems!
let task = tasks[indexPath.row]
let TaskSettingViewController = segue.destination as! ViewController
TaskSettingViewController.task = task
} else if identifier == "addTask" {
print("+ button tapped")
}
}
}
static func retrieveTasks() -> [Tasks] {
let fetchRequest = NSFetchRequest<Tasks>(entityName: "Tasks")
do {
let results = try managedContext.fetch(fetchRequest)
return results
} catch let error as NSError {
print("Could not fetch \(error)")
}
return []
}
Try this :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
if identifier == "displayCellDetail" {
print("Task View cell tapped")
CollectionViewCoreDataHelper.retrieveTasks()
let cell = sender as? YourCellName //Cell from which this segue is being performed
let indexPath = self.collectionView!.indexPathForCell(cell)
let task = self.tasks[indexPath.item] //Downcast to type of task
let objTaskSettingVC = segue.destination as! TaskSettingViewController
objTaskSettingVC.tasks = task
}
else if identifier == "addTask" {
print("+ button tapped")
}
}
}
For passing the data from one viewController to other is here
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
if identifier == "displayCellDetail" {
print("Task View cell tapped")
CollectionViewCoreDataHelper.retrieveTasks()
let cell = sender as UICollectionViewCell
let indexPath = self.collectionView!.indexPathForCell(cell)
let task = self.tasks[indexPath.row] as [ToDo]
let objTaskSettingVC = segue.destination as! TaskSettingViewController // ViewController in which you want to send the data
objTaskSettingVC.tasks = [task] //tasks is your variable which is having same type and defined in your TaskSettingViewController
}
else if identifier == "addTask" {
print("+ button tapped")
}
}
}
I have a TableView where I display all my data and each cell might have 1-2 buttons. I read many topics and understand how to add target for each button through my ViewController. Since these buttons will be forwarded to the same VC and display images, I have the following code. In my TableViewCell subclass I have 2 buttons
class CODetailsTicketCell: UITableViewCel {
var onButtonTapped: (() -> Void)? = nil
#IBAction func firstBtnTapped(_ sender: UIButton) {
if let onButtonTapped = self.onButtonTapped {
onButtonTapped()
}
print("First button was pressed")
}
#IBAction func secondBtnTapped(_ sender: UIButton) {
if let onButtonTapped = self.onButtonTapped {
onButtonTapped()
}
print("Second button was pressed")
}
}
In my ViewController in cellForRowAt indexPath I have the following code
let message = messages[indexPath.row]
if let cell = tableView.dequeueReusableCell(withIdentifier: "COTicketsCell", for: indexPath) as? CODetailsTicketCell {
cell.configureCell(openTickets: message)
cell.onButtonTapped = {
self.performSegue(withIdentifier: "toImageVC", sender: message)
}
return cell
In order to pass the data through segue I use the following code in prepareForSegue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toImageVC" {
let navigationController = segue.destination as? UINavigationController
if let targetController = navigationController?.topViewController as? ImageVC {
if let data = sender as? OpenTicketsData {
targetController.loadImageURL = URL(string: data.firstImageUrl)
}
}
}
}
Everything is working FINE but I can't check for button tag in prepareForSegue. Basically, currently both buttons send the same data
targetController.loadImageURL = URL(string: data.firstImageUrl)
How can I pass data based on the button pressed? I tried to do something like this but seems it's wrong and not working.
let button = sender as? UIButton
if let data = sender as? OpenTicketsData {
if button?.tag == 1 {
targetController.loadImageURL = URL(string: data.firstImageUrl)
} else if button?.tag == 2 {
targetController.loadImageURL = URL(string: data.secondImageUrl)
}
}
You can either separate it into 2 different events or
class CODetailsTicketCell: UITableViewCell {
var onButtonTapped: ((_ sender: UIButton) -> Void)? = nil
#IBAction func firstBtnTapped(_ sender: UIButton) {
if let onButtonTapped = self.onButtonTapped {
onButtonTapped?(sender)
}
print("First button was pressed")
}
#IBAction func secondBtnTapped(_ sender: UIButton) {
if let onButtonTapped = self.onButtonTapped {
onButtonTapped(sender)
}
print("Second button was pressed")
}
}
In your assignment of the onButtonTapped, remember to add [weak self] if you ever use self to avoid the retain cycle.
cell.onButtonTapped = { [weak self] sender in
if sender.tag == 1 {
// Do something
} else {
// Do other thing
}
}
Thanks for the help in advance. I don't know why when I try to make a phone call with a button function as below
#IBAction func phonebutton(_ sender: Any) {
guard let number1 = URL(string: "telprompt://\(phonenumber)") else { return }
UIApplication.shared.open(number1 as! URL, options: [:], completionHandler: nil)
}
it doesn't show anything.
To explain more, the String value "phonenumber" is transferred from another tableview cell prepare for segue function as below
override func prepare(for segue: UIStoryboardSegue, sender: Any? ) {
if (segue.identifier == "indooradventureseg"){
let transfertopage = segue.destination as! indooradventuretablecellViewController
transfertopage.phonenumber = info[sender as! Int][4] as! String
}
Here's the code in the ViewController where I put my call button in
class indooradventuretablecellViewController: UIViewController {
var phonenumber = "31617543"
override func viewDidLoad() {
super.viewDidLoad()
phonenumberlabel.text = phonenumber
phonebuttonlabel.setTitle(phonenumber, for: .normal)
}
However, if I put any other String initiated within the ViewController or simply put the phone number on the button function like below, the phone call function does work.
#IBAction func phonebutton(_ sender: Any) {
guard let number1 = URL(string: "telprompt://12345678") else { return }
UIApplication.shared.open(number1 as! URL, options: [:], completionHandler: nil)
}
Please help. Thanks!
Im using contact framework to fetch user contact's name, number, email, and contact image, the data is being fetched. All I am able to display right now is only one of the keys in the table view, like either the name number or email, I wish to have the name be clicked and then segue to the other view controller where the rest of the properties show up. but I am having trouble displaying it on the other viewController. I have tried using the get contacts method in the prepareforsegue function, did not seem to work The code I am using to fetch contacts is
func getContacts() {
let store = CNContactStore()
if CNContactStore.authorizationStatusForEntityType(.Contacts) == .NotDetermined {
store.requestAccessForEntityType(.Contacts, completionHandler: { (authorized: Bool, error: NSError?) -> Void in
if authorized {
self.retrieveContactsWithStore(store)
}
})
} else if CNContactStore.authorizationStatusForEntityType(.Contacts) == .Authorized {
self.retrieveContactsWithStore(store)
}
tableView.reloadData()
}
func retrieveContactsWithStore(store: CNContactStore) {
do {
let groups = try store.groupsMatchingPredicate(nil)
// let predicate = CNContact.predicateForContactsInGroupWithIdentifier(groups[0].identifier)
let containerId = CNContactStore().defaultContainerIdentifier()
let predicate: NSPredicate = CNContact.predicateForContactsInContainerWithIdentifier(containerId)
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName), CNContactEmailAddressesKey, CNContactPhoneNumbersKey,CNContactImageDataAvailableKey,CNContactThumbnailImageDataKey]
print(CNContactEmailAddressesKey.capitalizedString)
let contacts = try store.unifiedContactsMatchingPredicate(predicate, keysToFetch: keysToFetch)
self.objects = contacts
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
} catch {
print(error)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if identifier == "DetailContact" {
}
}
}
As i said in my comments, create a new contact object in your other controller. For example: yourContactObject and then set some value to it in the prepare for segue as follows:
var selectedContact;// Your contact which you will set in the did select of the other controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if identifier == "DetailContact" {
let controller = segue.destinationViewController
controller.yourContactObject = selectedContact.
}
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedContact = objects[indexPath.row]
}
Instead of trying to re-do the query in prepareForSegue, make a property on your other view controller, say var contact: CNContactStore! and then observe which row gets selected in the table view. So in didSelectRowAtIndexPath you can do the following...
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let contactSelected = yourDataModel[indexPath.row] //now you know which contact was selected
self.performSegueWithIdentifier("DetailContact", sender: contactSelected)
/**you can pass the selected contact as the sender argument,
or set it as a class-level variable,
or use a delegate or notification, all would work */
}
And then in your prepareForSegue method, could do the following...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if identifier == "DetailContact" {
let controller = segue.destinationViewController
controller.contact = sender as! CNContactStore
}
}
}