I'm new to Swift and Xcode in general. I'm trying to make an app similar in essence to Twitter. My issue is that after my Table View Controller for my timeline I have two pages/View Controllers: one with a picker to choose a specific house and the next to make a text post.
On the Second View Controller's code with the text, I try to save an object with the post's content and the selected house to Parse. The content is saved just fine. But I get errors when I try to do the same to the house (because it's from a different page)
I'm positive this is a very simple, obvious thing I am missing....
Thanks!
Edit: I'm doing anonymous users btw if that's relevant
code for the picker from the first view controller
#IBOutlet weak var label: UILabel!
#IBOutlet weak var picker: UIPickerView!
let pickerData = ["Adams","Cabot","Currier","Dunster","Eliot","Kirkland","Leverett","Lowell","Mather","Pforzheimer","Quincy","Winthrop"]
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView,
numberOfRowsInComponent component: Int) -> Int {
return pickerData.count
}
func pickerView(pickerView: UIPickerView,
titleForRow row: Int,
forComponent component: Int) -> String!{
return pickerData[row]
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int){
self.label.text = pickerData[row]
}
override func viewDidLoad() {
super.viewDidLoad()
// label is "Adams" when first loaded
label.text = pickerData[0]
picker = UIPickerView()
}
code to from the second view control to Save it to Parse
#IBAction func PostText(sender: AnyObject) {
// establish object to save in Parse CHANGE USER TO HOUSE
var post:PFObject = PFObject(className: "Posts")
post["content"] = PostTextView.text
post["user"] = PFUser.currentUser()
post["house"] = pickerData[row].text
post.saveInBackgroundWithTarget(nil , selector: nil)
// return to navigation screen
self.navigationController?.popToRootViewControllerAnimated(true)
}
error is unresolved identifier (because they're defined in another view controller?)
Neither pickerData nor row belong to Second View Controller (i.e. they're unresolved identifiers) so you have to get an instance of First View Controller to access the pickerData variable and to find picker's currently selected "row."
As long as you're using a UINavigationController you can find any previous view controller in the stack. If FirstViewController is immediately preceding the current SecondViewController, you can find it one index behind on the navigation stack, ex:
let navigationController: UINavigationController = self.navigationController!
let numberOfViewControllers = navigationController.viewControllers.count
var firstViewController: FirstViewController = navigationController.viewControllers[numberOfViewControllers - 2] as FirstViewController
let row = firstViewController.picker.selectedRowInComponent(0)
post["house"] = firstViewController.pickerData[row]
But generally speaking, it's probably cleaner to send your view controller object forward instead of pulling it from the nav stack.
If you're using a Storyboard, for example, you can update the prepareToSegue method in your first view controller to share your current FirstViewController instance with your second view controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// (Uncomment the if block and specify a segue if this isn't the
// segue leading from your view controller.)
//if segue.identifier == "yourSegueName" {
let secondViewController = segue.destinationViewController as SecondViewController
secondViewController.firstViewController = self;
//}
}
Then in your second view controller, add a corresponding class variable to hold the first view controller object:
class SecondViewController: UIViewController {
var firstViewController: FirstViewController!
That way you can access its values within FirstViewController like so:
let row = firstViewController.picker.selectedRowInComponent(0)
post["house"] = firstViewController.pickerData[row]
If you're using a UINavigationController without a storyboard, you can similarly set the SecondViewController's FirstViewController variable upon the SecondViewController's creation, ex:
SecondViewController *secondViewController = [[SecondViewController alloc] init];
secondViewController.firstViewController = self;
[self.navigationController pushViewController:secondViewController animated:YES];
Related
I am trying to pass value I get from Firebase to another tableView. I get 2 values from Firebase - "Brands" and "Products". I am trying to make like car app. If you click on Ford then new tableView will appear and shows all the Ford models. This is what I've done so far.
like this I get Brands from Firebase:
func parseSnusBrands(){
let ref = FIRDatabase.database().reference().child("Snuses").child("Brands")
ref.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if snapshot.exists() {
if let all = (snapshot.value?.allKeys)! as? [String]{
for a in all{
if let products = snapshot.value![a] as? [[String:String]]{
self.snusBrandsArray.append(["key":a,"value":products])
}
}
self.snusBrandsTableView.reloadData()
}
}
})
}
And like this I detect which cell is clicked and print the product that belongs to the clicked Brand:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
print("products at \(indexPath.row) --> \(snusBrandsArray[indexPath.row]["value"])")
}
How to pass the (snusBrandsArray[indexPath.row]["value"]) to new tableView? I tried using segues and looking for tutorials like "How to pas value between viewControllers" but I am out of luck. Right now I have 2 tableViewController.swift files and one tableViewCustomCell.swift file. Do I need some more files?
For send data, first of all declare your variable in 2nd view controller..
var productsValue = [[String:String]]()
and in 1st viewcontroller
var valueTopass = [[String:String]]()
Than in didSelectRowAtIndexPath, take a value in one valueTopass
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("products at \(indexPath.row) --> \(snusBrandsArray[indexPath.row]["value"])")
if let products = snusBrandsArray[indexPath.row]["value"] as? [[String:String]]{
valueTopass = products
performSegueWithIdentifier("toProducts", sender: self)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if (segue.identifier == "toProducts") {
var viewController = segue.destinationViewController as! SnusProductsTableViewController
viewController.productsValue = valueTopass
print(productValues)
}
}
You need to use Segues to pass the data forward.
To pass data from the current view controller to the next new view controller using segues, first create a segue with an identifier in the relevant storyboard. Override your current view controller's prepareForSegue method. Inside the method check for the segue you just created by its identifier. Cast the destination view controller and pass data to it by setting properties on the downcast view controller.
Setting an identifier for a segue:
Segues can be performed programatically or using button action event set in the storyboard by ctrl+drag to destination view controller.
You can call for a segue programatically, when needed, using segue identifier in the view controller:
func showDetail() {
performSegueWithIdentifier("showDetailingSegue", sender: self)
}
You can configure segue payload in the override version of prepareForSegue method. You can set required properties before destination view controller is loaded.
Swift
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetailingSegue" {
let controller = segue.destinationViewController as! DetailViewController
controller.isDetailingEnabled = true
}
}
DetailViewController is the name of the second view controller and isDetailingEnabled is a public variable in that view controller.
To expand on this pattern, you can treat a public method on DetailViewController as a pseudo initializer, to help initialize any required variables. This will self document variables that need to be set on DetailViewController without having to read through it's source code. It's also a handy place to put defaults.
Swift
func initVC(isDetailingEnabled: Bool) {
self.isDetailingEnabled = isDetailingEnabled
}
Why not pass the whole dictionary with all the contents from firebase to the new VC using prepare for segue?
And then store the dict in the destinationVC data model?
That should do the trick.
I've got a table view with X rows and i want that if I tap one of the rows it displays a view controller with a different value each.
I know that i have to use prepareForSegue but i don't know how.
I'll make an example:
There's the table view with 4 rows.
I tap the first row and it opens a view controller with a label that shows "1".
I tap the second row and it opens a view controller with a label that shows "2".
And so on.
I don't want to create milions of view controllers, i'd like to have only one of them that changes every time.
You'll make me a big favor.
Thanks
First you need to detect the user's selection.
This is done with the -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath function.
Once you have detected the selected row, you need to invoke the the next view controller. You can do this by instantiating the viewController you want to show using the storyboard:
UIViewController* viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"Your VC identifier here"];
To pass information to another controller you can create a property and set it like any other object:
[viewController setCustomArray:#[]]
Finally you need to present the view controller:
[self presentViewController:viewController animated:YES completion:nil]
You need to add the following in your tableViewController class
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
text = indexPath.row
performSegueWithIdentifier("YourSegue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "YourSegue" {
let yourOtherVC = segue.destinationViewController as? SecondViewController
yourOtherVC?.text = text
}
}
In your SecondViewController class, add the following:
class SecondViewController: UITableViewController {
var text:String = ""
#IBOutlet weak var myLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if text != nil {
myLabel.text = text
}
}
Hope this helps. :)
I cannot seem to find the connection to have an external class manage a view in a ViewController. I'm new to iOS and have spent considerable looking for a solution. Simple Example:
Subclass of UIPickerView
I create a file that is a subclass of UIPickerView and have it conform to the PickerView delegate and datasource.
class MyPickerView: UIPickerView, UIPickerViewDelegate, UIPickerViewDataSource {
//In here I conform to all the required methods...no problems there
}
Main View Controller with Outlet for the PickerView
In my MainViewController, I create an outlet for my picker view. Also, in the StoryBoard I hookup the "custom class" for my Picker View to MyPickerView above.
class MainViewController: UIViewController {
#IBOutlet weak var myPickerView: UIPickerView!
override func viewDidLoad() {
//how do I hookup my picker view class
}
}
My Questions:
How do I tell my MainViewController that my subclass "MyPickerView" is managing the picker view for it?
How did I enable communication between the subclass and the view controller?
---------------------
UPDATE: FINAL SOLUTION Incorporating #Oscar's Answer
#Oscar's suggestion below was great. To clarify, I wanted my PickerView subclass to be the UIPickerView Delegate because the Picker will always have the same UI and there are many PickerView delegate methods for UI. (attributedTitleForRow, widthForComponent, rowHeightForComponent, etc) I don't want to call those delegate methods in every ViewController that uses this PickerView.
Now, when the PickerView "didSelectRow" is called, we need to notify our ViewController and pass the value that was selected. To get this to work, I used a Protocol. (summarized below) This topic took me a while to learn, but is crucial, so I suggest spending time with Protocols & Delegation if this doesn't make sense.
Create a protocol in the PickerView with a func that will be used to talk to ViewControllers that present this PickerView:
protocol MyPickerViewProtocol {
func myPickerDidSelectRow(selectedRowValue:Int?)
}
In the ViewController presenting the PickerView, conform to your PickerView protocol. By doing so, you will have to place the func myPickerDidSelectRow somewhere in your ViewController:
class MyViewController: MyPickerViewProtocol {
func myPickerDidSelectRow(selectedRowValue:Int?) {
//do stuff to update your ViewController
}
}
#Oscar's answer below will hookup the picker view to your view controller, but there's one last thing. In order for the PickerView to talk back, you will want a property in your PickerView, that is a reference to the view controller it's contained in. Here's the PickeView and ViewController classes in perspective:
//PickerView Subclass ------------------
protocol MyPickerViewProtocol {
func myPickerDidSelectRow(selectedRowValue:Int?)
}
class MyPickerView: UIPickerView {
//Note: this var is of type your Picker protocol above. Because the ViewController will conform to the protocol, this var will be the reference (or the pointer) to the protocol func you implement in your ViewController...which is myPickerDidSelectRow
var propertyThatReferencesThisViewController:MyPickerViewProtocol?
}
//ViewController Class ----------------
myPicker = MyPickerView()
myPickerView.dataSource = myPicker //note: myPickerView is the outlet of type UIPickerView in your ViewController
myPickerView.delegate = myPicker
//HERE'S THE PROPERTY from our PickerView subclass that will point to this ViewController's protocol methods that we implemented. From the MyPickerViewProtocol
myPicker.propertyThatReferencesThisViewController = self
Now when a row is selected in our PickerView, let's tell the ViewController using our property: propertyThatReferencesThisViewController
class MyPickerView: UIPickerView {
//This Property points to the ViewController conforming to the protocol. This property will only be able to access the stuff you put in the protocol. It won't access everything in your ViewController
var propertyThatReferencesThisViewController:MyPickerViewProtocol?
//didSelectRow UIPickerView Delegate method that apple gives us
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
//get your picker values that you need
let theRowValue = someArray[row]
propertyThatReferencesThisViewController?.myPickerDidSelectRow(theRowValue)
//the ViewController func will be called passing the row value along
}
}
Subclass Pickerview
class MyPickerView: UIPickerView, UIPickerViewDataSource, UIPickerViewDelegate {
var oficinas = ["oficina 1", "Oficinas 2", "Oficina 3"]
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return oficinas.count
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return oficinas[row]
}
}
Main View Controller with Outlet for the PickerView
class MainViewController: UIViewController {
#IBOutlet weak var myPickerView: UIPickerView!
var pickerOficinas: MyPickerView!
override func viewDidLoad() {
//how do I hookup my picker view class
pickerOficinas = MyPickerView()
myPickerView.delegate = pickerOficinas
myPickerView.dataSource = pickerOficinas
}
}
I think you may have got hold of the wrong end of the stick!
Why do you want to make the picker its own delegate? The point of having a delegate is that it can tell its delegate what has been selected etc.
I would think that what you should be doing is making your view controller conform to UIPickerViewDelegate and make it the delegate of the picker and put the logic for whatever you want to happen when an item is picked in those delegate methods. I can't see any other way of 'telling' your view controller about the picker.
Also, if you reference to your picker is weak, then unless you are holding a strong reference to it somewhere else, at all times (eg it is part of the view hierarchy) it will be deallocated.
I have been looking all over for an answer for this...I do not know how to pass data through an unwind segue. I know I am supposed to use prepareForSegue but I am not sure what I am supposed to put in. I am trying to pass a variable (myLabel) from a UIPickerView in one class:
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
myLabel.text = pickerData[row]
}
to another class. I have the unwind segue code already but do not know where to put the prepareForSegue and what to put in it.
Let me know if my question needs more clarification.
If I understand your question correctly, you should do something like this:
class MyPickerController : UIViewController {
#IBOutlet weak var myLabel: UILabel!
var pickerData:[String]
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
myLabel.text = pickerData[row]
performSegueWithIdentifier( "mySegueName", sender: self )
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let myController = segue.destinationController as? MyDestinationViewController {
myController.text = myLabel.text
}
}
That will pass the label text to the MyDestinationViewController instance.
Note that I don't pass the UILabel object, but the text.
You can not place that UILabel object in the destination controller interface, but you can use the text an set another label with it, for example.
prepareForSegue is called in the class of the view controller that you will be unwinding from (the view controller that was previously pushed or presented, and is now being popped or dismissed). The destination view controller of the segue is the view controller that will appear as a result.
I have a ViewController with a UIPickerView as a single control myPickerView which is of a class MyPickerView which I created as a sub-class of UIPickerView. I invoke myPickerView in ViewController viewDidLoad by myPickerView.viewDidLoad. However, this does not execute the source functions of MyPickerView.
I need a clarification of how I can make this work. My reason for MyPickerView is that it has a lot of special code that I did not want to clutter up the main ViewController. See the example code below:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myPickerView: MyPickerView!
override func viewDidLoad() {
super.viewDidLoad()
myPickerView.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
import UIKit
var gSep = ","
class MyPickerView: UIPickerView , UIPickerViewDataSource, UIPickerViewDelegate {
var pickerData = [[" "],[" "]]
var category = [""]
var subCategory = [""]
var dictMenuList = [String:String]()
//MARK:- category/subcategory picker
func viewDidLoad() {
println("MyPickerView: viewDidLoad")
dictMenuList = ["Medical":"Sub-Cat 1.1,Sub-Cat 1.2,Sub-Cat 1.3,Sub-Cat 1.4,Sub-Cat 1.5,Sub-Cat 1.6,Sub-Cat 1.7",
"Taxes": "Sub-Cat 2.1,Sub-Cat 2.2,Sub-Cat 2.3,Sub-Cat 2.4",
"Bills": "Sub-Cat 3.1,Sub-Cat 3.2,Sub-Cat 3.3,Sub-Cat 3.4,Sub-Cat 3.5,Sub-Cat 3.6,Sub-Cat 3.7"]
println("MyPickerView dictMenuList: \(dictMenuList)")
self.reloadAllComponents()
let firstKey = self.loadPickerWithCategory(0)
self.loadPickerWithSubCategory(firstKey)
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
println("MyPickerView: numberOfComponentsInPickerView \(pickerData.count)")
return pickerData.count
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerData[component].count
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if component == 0 {
let selectedKey = category[row]
loadPickerWithSubCategory(selectedKey)
}
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return pickerData[component][row]
}
func pickerView(pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusingView view: UIView!) -> UIView
{
var pickerLabel = UILabel()
pickerLabel.textColor = UIColor.blackColor()
pickerLabel.text = pickerData[component][row]
pickerLabel.font = UIFont(name: pickerLabel.font.fontName, size: 17)
pickerLabel.textAlignment = NSTextAlignment.Center
return pickerLabel
}
func loadPickerWithCategory (row: Int) -> String{
println("loadPickerWithCategory")
category = [String](dictMenuList.keys)
println("MyPickerView: category: \(category)")
if category.isEmpty {
return ""
}
let n1 = dictMenuList.count
pickerData[0].removeAll(keepCapacity: true)
for i in 0 ..< n1
{
pickerData[0].append(category[i])
}
return category[row]
}
func loadPickerWithSubCategory (key: String) {
println("MyPickerView: loadPickerWithSubCategory")
let x = dictMenuList[key]
subCategory = x!.componentsSeparatedByString(gSep)
let n1 = subCategory.count
pickerData[1].removeAll(keepCapacity: true)
if subCategory.isEmpty {
return
}
for i in 0 ..< n1
{
pickerData[1].append(subCategory[i])
}
self.reloadAllComponents()
}
}
The method viewDidLoad is a view controller method, not a view method. A UIPickerView is a subclass of UIView, not UIViewController, so the system will not call your viewDidLoad method.
You need to override one or more of the init methods.
If you're loading your picker view from a Storyboard or XIB, you probably want to override initWithCoder.
If you're creating your picker in code, you probably want to override initWithFrame.
I sometimes create a method setup that I call from both initWithCoder: and from initWithFrame:. That way my setup code gets called regardless of how the view object is loaded.
I vaguely remember reading that there is a better way of handling this dueling initializers problem in Swift, but I don't remember what it is. (I'm still learning Swift.)
EDIT:
It just occurs to me that you can use the method awakeFromNib to do setup after your view has been loaded and all of it's outlets are set up. That's roughly equivalent to the viewDidLoad call for view controllers. I should have thought of that sooner.
(awakeFromNib is a method of NSObject, so it's a bit hard to find if you don't know it exists.)
First of all viewDidLoad() is a method of the UIViewController class and is called after the controller's view is loaded into the memory. Read more here. You can not use it in views.
So you should implement an init method inside your custom picker class. I'd recommend to override initWithFrame and initWithCoder and set up your component there.
And you will initialize your custom picker like this:
myPickerView = MyPickerView(frame: yourFrame)