I am trying to segue to a new viewcontroller in swift 3. I am also trying to send an array to the new viewcontroller, I am doing this programmatically as well. here is the code for my segue:
performSegue(withIdentifier: "AlbumSegue", sender:self) {
let destinationVC = segue.destination as! DestinationViewController
destinationVC.recievedArray = selectedAlbumArray
The error I get is "Extra Argument 'Sender' in Call"
So if I take out the sender part and my code becomes:
performSegue(withIdentifier: "AlbumSegue") {
let destinationVC = segue.destination as! DestinationViewController
destinationVC.recievedArray = selectedAlbumArray
}
I then get an error saying "use of unresolved Identifier 'segue'"
But I've found code examples like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! DestinationViewController
destinationVC.receivedString = stringToPass
}
}
Basically both of these problems are in TEMPLATES for doing this so I don't know why either errors are occurring because according to answers on StackOverflow these aren't errors at all.
Related
I have a problem showing a tabbarVC.
Here is the codes:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let tabBarVC = UITabBarController()
guard let vc1 = storyboard?.instantiateViewController(identifier: "FirstController") as? FirstController else {
print("failed to get vc1 from Storyboard")
return
}
guard let vc2 = storyboard?.instantiateViewController(identifier: "SecondController") as? SecondController else {
print("failed to get vc2 from Storyboard")
return
}
guard let vc3 = storyboard?.instantiateViewController(identifier: "ThirdController") as? ThirdController else {
print("failed to get vc3 from Storyboard")
return
}
let vc4 = UINavigationController(rootViewController: vc1)
let vc5 = UINavigationController(rootViewController: vc2)
let vc6 = UINavigationController(rootViewController: vc3)
vc4.title = "XXX"
vc5.title = "YYY"
vc6.title = "ZZZ"
tabBarVC.setViewControllers([vc4,vc5,vc6], animated: false)
tabBarVC.modalPresentationStyle = .fullScreen
self.present(tabBarVC, animated: true)
}
}
The tabbar has shown correctly, but I got a warning of "Unbalanced calls to begin/end appearance transitions for <tabbarcontroltest.ViewController:" which I don't understand.
Also I have tried to change
tabBarVC.modalPresentationStyle = .fullScreen
to
tabBarVC.modalPresentationStyle = .overFullScreen
And, then I don't have this warning, but instead, when I try to close the app by home button,
I got another warning as
tabbarcontroltest[Presentation] Attempt to present on <tabbarcontroltest.ViewController> (from <tabbarcontroltest.ViewController) which is already presenting .
I guess there is something wrong with the presentation style? Or is there something else wrong?
Thanks
found the solution by myself. the correct way is as follows:
tabBarVC.modalPresentationStyle = .fullScreen
tabBarVC.view.frame = self.view.bounds
addChild(tabBarVC)
view.addSubview(tabBarVC.view)
tabBarVC.willMove(toParent: self)
I want to add a new To Do item when i press the add button,but i don't want to switch to another page.
press the add button to add a new row in the table view,input something and press the done button,it will save.
somebody suggests me to save the cells data to Model,but i don't know how to write this.
Who can help me?
import UIKit
import CoreData
class ToDoViewController: UIViewController {
var items: [NSManagedObject] = []
#IBOutlet weak var tableView: UITableView!
#IBAction func addItem(_ sender: UIBarButtonItem) {
//***How to write this code***
}
#IBAction func done(_ sender: UIBarButtonItem) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "ToDo", in: managedContext)!
let item = NSManagedObject(entity: entity, insertInto: managedContext)
//***let list = the current textView's text
//how to get the textView's text and assign it to a value.***
item.setValue(list, forKeyPath: "summary")
do {
try managedContext.save()
items.append(item)
} catch let error as NSError {
print("Could not save.\(error),\(error.userInfo)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self,forCellReuseIdentifier: "Cell")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "ToDo")
do {
items = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch.\(error),\(error.userInfo)")
}
}
}
extension ToDoViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let textView = UITextView(frame: CGRect(x: 0, y: 0, width: cell.frame.size.width, height: cell.frame.size.height))
cell.addSubview(textView)
textView.text = item.value(forKey: "summary") as? String
return cell
}
}
Ok so If my understanding is right you need a new row to be added if they create a new entry into your Core Data. So in your viewWillAppear you're doing a fetch. What I think you need is a:
var fetchResultController : NSFetchedResultsController<YourType>!
Then using this fetch controller you want to do the following when fetching:
private func GetToDoEntries(){
if let appDele = UIApplication.shared.deletgate as? AppDelegate{
let givenContext = appDele.persistantContainer.viewContex
let entryFetchRequest : NSFetchRequest<YourType> = YourType.fetchRequest()
let sortDescriptors = NSSortDescriptor(key: "yourEntrySortKey" , ascending: true)
entryFetchRequest.sortDescriptors = [sortDescriptors]
fetchResultController = NSFetchedResultsController(fetchRequest: entryFetchRequest, managedObjectContext: givenContext, sectionNameKeyPath: nil, cacheName: nil)
fetchResultController.delegate = self
do{
//Gets fetched data based on our criteria
try fetchResultController.performFetch()
if let fetchedEntries = fetchResultController.fetchedObjects{
items = fetchedEntries as? WhateverToCastTo
}
}catch{
print("Error when trying to find entries")
}
}
}
First I'm sorry but I've just written this here is stackOverflow. So what you're doing is using a fetch result controller instead of a traditional search. You are required to have the sort descriptor as well and you can try to get the results and cast them to your items or as a NSManagedObject.
Now we're not done though. Your script needs to inherit from some behaviour. At the top of your class
class ToDoViewController : UIViewController, NSFetchedResultsControllerDelegate
You need the delegate as you can see in the first block of code because we're assigning it. Now we're almost there. You just need some methods to update the table for you and these come with the delegate we just inherited from.
//Allows the fetch controller to update
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
//Allows new additions to be added to the table
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type{
case .insert:
if let _newIndexPath = newIndexPath{
tableView.insertRows(at: [_newIndexPath], with: .fade)
}
case .update:
if let index = indexPath{
tableView.reloadRows(at: [index], with: .fade)
}
default:
budgetEntryTable.reloadData()
}
if let fetchedObjects = controller.fetchedObjects{
items = fetchedObjects as! [NSManagedObject (Or your cast type)]
budgetEntryTable.reloadData()
}
}
//Ends the table adding
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
So there are 3 methods here. The first and second are very self explanatory. They begin and end the updates on your tableView. I'd also recommend that you change the name of your tableView to something other than "tableView" just for clarity.
The method in the middle uses a switch. My example is missing the "Move" and "Delete" options as I didn't required them in my project but you can add them to the switch statement.
The insert is checking the newIndexPath to see if there is one. If so then we add an array of the amount of rows required at the newIndexPath.
The update just checks the current index paths and then reloads the data on them incase you updated your data model.
I hope this is what you were looking for. Good luck! I'll try and help more if you need it but that should get you started.
I have an endpoint which takes in a phone number and sends a code to the number, but also returns that same message to the data section of the session that called it.
All of that works, but the problem I'm having is that, after the session makes the call, I'm segueing to the next screen and i'm passing that code into the next controller. But i think the api is responding too slow, so by time the segue (and prep for segue) has happened the code has not been returned yet. How can i fix this?
let scriptURL = "https://---------------/api/verify/sms?"
let urlWithParams = scriptURL + "number=\(phone.text!)"
let myUrl = NSURL(string: urlWithParams)
let request = NSMutableURLRequest(url: myUrl! as URL)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
//print(error?.localizedDescription)
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as AnyObject
self.currentCode = json["code"]!! as! String //-> This is the code the is returned from the api call
}catch{
print("error with serializing JSON: \(error)")
}
}
task.resume()
self.performSegue(withIdentifier: "toVerifyCode", sender: (Any?).self)
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier == "toVerifyCode"{
let newController = segue.destination as! verifyCodeController
newController.code = self.currentCode
}
}
The problem is that you placed self.performSegue(withIdentifier: "toVerifyCode", sender: (Any?).self) not in the closure.
So, you have to place it like this:
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
//print(error?.localizedDescription)
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as AnyObject
//on main thread
DispatchQueue.main.async {
self.currentCode = json["code"]!! as! String //-> This is the code the is returned from the api call
self.performSegue(withIdentifier: "toVerifyCode", sender: (Any?).self)
}
}catch{
print("error with serializing JSON: \(error)")
}
}
task.resume()
Also, please note that your closure is executed asynchronously, so I wrapped the call to be executed on main thread by using GCD.
After clicking on a accessoryButton in TableView, it will redirect me first to PrepareforSegue function and then to accessoryButtonTappedForRowWith Function which creating error in my code.
Please have a look into my code:
var indexPathAccessory: Int?
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
indexPathAccessory = indexPath.row
}
indexPathAccessory contains the value of row where Accessorybutton was clicked.
The second function is:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Add" {
let nc = segue.destination as! UINavigatorController
controller = nc.topViewController as! AVC
//Comment 1
controller.name = span[indexPathAccessory]
//Comment 2
controller.delegate = self
}
}
I am getting error cause after clicking on accessorybutton, it is redirecting me to prepareforSegue function first and then to accessoryButtonTappedForRowWith and due to value of indexPathAccessory which still nil, I am getting this error
Will it be possible to jump first on accessoryButtonTappedForRowWith to get the value of indexPathAccessory and then to prepareForSegue?
if yes, problem will be solved.
I tried to add one test to return if indexPathAccessory is nil.
application run without error but not in a proper way:
I can see that controller.name is equal to "test", but when I am on AVC View controller, name became nil
Any advise?
One of the solution can be - remove the segue from the storyboard which causes the control going out of your hand and write the performSegue inside your accessoryButtonTappedForRowWith method.
Finally it worked by deleting the accessoryButtonTappedForRowWith function, and adding the following code to prepareforSegue functions:
if let indexPath = tableView.indexPath(for: sender as! UITableViewCell) {
controller.beamName = "Test"
indexSpanAccessory = indexPath.row
}
Thanks to Apple my iOS 9 Project 'Swift 2.3' is completely unusable with iOS 10's 'Swift 3'...
I fixed almost everything except that I am having issue with using NSURLSession, Xcode is telling me that it has been renamed to URLSession, if I rename it Xcode will tell me:
use of undeclared type URLSession
Foundation is imported.
What is the issue?!
For example I am using it this way...
lazy var defaultSession: URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier: "reCoded.BGDownload")
configuration.sessionSendsLaunchEvents = true
configuration.isDiscretionary = true
let session = URLSession(configuration: configuration, delegate: self, delegateQueue, queue: nil)
return session
}()
and even with the delegate methods the same issue.
Try using Foundation.URLSession where ever you use URLSession.
/Got it to work/ In some cases try to copy your code somewhere else then remove everything in your class that uses URLSession then type the session methods again and put back your copied code you should be fine.
Update your URLSessin functions with;
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
self.data.append(data as Data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON()
}
}
I can explain how but by playing around with the code I got this to work in SWIFT 3 after two days of frustration. I guess SWIFT 3 removed a lot of unnecessary words.
let task = Foundation.URLSession.shared.dataTask(with: <#T##URL#>, completionHandler: <#T##(Data?, URLResponse?, Error?) -> Void#>)
Here's where I am right now. It's not perfect but works maybe half of the time.
First, in the class where my URLsession is defined:
import Foundation
class Central: NSObject, URLSessionDataDelegate, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDownloadDelegate {
I don't think all of that is necessary, but there it is. Then here is the function that is called by my background fetch:
func getWebData() {
var defaults: UserDefaults = UserDefaults.standard
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: "myBGconfig")
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
urlString = "https://www.powersmartpricing.org/psp/servlet?type=dayslider"
if let url = URL(string: urlString) {
let rateTask = backgroundSession.downloadTask(with: URL(string: urlString)!)
rateTask.taskDescription = "rate"
rateTask.resume()
}
When the task comes back:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL ) {
if downloadTask.taskDescription == "rate" { // I run 2 web tasks during the session
if let data = NSData(contentsOf: location) {
var return1 = String(data: data as! Data, encoding: String.Encoding.utf8)!
DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + 0.2){
var defaults: UserDefaults = UserDefaults.standard
defaults.set(myNumber, forKey: "electricRate") // myNumber is an extract of the text in returned web data
defaults.set(Date(), forKey: "rateUpdate")
defaults.synchronize()
self.calcSetting() //Calls another function defined in the same class. That function sends the user a notification.
let notificationName = Notification.Name("GotWebData")
NotificationCenter.default.post(name: notificationName, object: nil)
} // Closes the Dispatch
}
if session.configuration.identifier == "myBGconfig" {
print("about to invalidate the session")
session.invalidateAndCancel()
}
}
I haven't figured out yet how to kill the session when BOTH tasks have completed, so right now I kill it when either one is complete, with invalidateAndCancel as above.
And finally, to catch errors:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didCompleteWithError: Error?) {
if downloadTask.taskDescription == "rate" {
print("rate download failed with error \(didCompleteWithError)")
}
if downloadTask.taskDescription == "other" {
print("other download failed with error \(didCompleteWithError)")
}
downloadTask.resume() // I'm hoping this retries if a task fails?
}
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
if let error = error as? NSError {
print("invalidate, error %# / %d", error.domain, error.code)
} else {
print("invalidate, no error")
}
}