I have structs as follows in a First Collection View Controller
struct Area{
var name = String()
var image = String()
}
var area = [Area]()
and in Second Collection View Controller
struct AreaSelected {
var imageSelected = String()
}
var areaSelected = [AreaSelected]()
I want to display image selected from First Collection View Controller in the Second Collection View Controller.
So I did this for navigating them to Second Collection View Controller at didSelectItemAt indexPath
let indexPaths = self.areaCV!.indexPathsForSelectedItems!
var indexPath = indexPaths[0] as IndexPath
let detailViewController = self.storyboard?.instantiateViewController(withIdentifier: "SVC") as? SecondViewController
detailViewController?.areaSelected = [self.area[(indexPath as NSIndexPath).item]]
Them I am getting following compiler error
Cannot convert value of type 'Area' to expected element type
'AreaSelected'
How do I get rid of this error?
Basically
let selectedArea = self.area[(indexPath as NSIndexPath).item]
detailViewController?.areaSelected = [AreaSelected(imageSelected: selectedArea.name)]
PS: You are using different (incompatible) types in different view controllers so definitely you can't assign it directly to each other.
PSS: Much easier, cleaner and better to setup segue on CellSelected between ViewControllers, and assign areaSelected in func prepare(for segue:UIStoryboardSegue, sender: Any?)
The error message is pretty clear, your first struct is type: FirstCollectionViewController.Area and the second has a type SecondCollectionViewController.AreaSelected. The two are as different as Int and String, you can't assign one to another as you can't do let number: Int = "Of course no". However you can define a common type:
protocol AreaDescription {
var image: String { get }
}
class FirstVC: ... {
struct Area: AreaDescription {
var name: String
var image: String
}
}
class SecondVC: ... {
struct AreaSelected: AreaDescription {
var name: String
var image: String
}
}
And set your property as:
var areaSelected = [AreaDescription]()
Related
I'm doing a network request twice, in the view controllers connected to a tab bar. If I can do the network request directly in the tab bar controller, I can pass the data from there in just one network request.
I tried this but it is giving a lot of errors.
- var pages : [page] = [] is an array of structs that I want to pass. It is working fine, I just need to figure out the passing part.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let feedVC = segue.destination as! FeedViewController
let categoriesVC = segue.destination as! CategoriesGridViewController
feedVC.pages = pages as! [[String:Any]]
categoriesVC.pages = pages [[String:Any]]
}
Screenshot of my StoryBoard
I fetch from a database of images and their information. I make an array of structs and use that to display the data in the App. All of that is working fine.
Page -
struct page {
var urlsArray : [String]
var caption : String
var title : String
var time : String
}
Without seeing what data you are actually trying to pass, I would guess you should create a Class (not struct) of type Page (or whatever you want to call it) and pass an array of [Page] through the view controllers. Here's an example of how to set it up:
class Page {
private var _title: String
private var _otherVariable: Int
var title: String {
return _title
}
var otherVariable: Int {
return _otherVariable
}
init(title: String, otherVariable: Int) {
self._title = title
self._otherVariable = otherVariable
}
}
//IN VIEW CONTROLLER 1
//when you get the data, set data for a Page
let item = Page(title: "Example", otherVariable: 12345)
let item2 = Page(title: "Example2", otherVariable: 67890)
//call this to segue
func segueToFeedViewController() {
let dataToSendToNextViewController: [Page] = [item, item2] //add data here
performSegue(withIdentifier: "ToFeedViewController", sender: dataToSendToNextViewController)
}
//use if let for safety
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let newVC = segue.destination as? FeedViewController {
assert(sender as? [Page] != nil)
newVC.pages = sender as! [Page]
}
}
I have the following seque. This goes into the ViewImages storyboard.
if segue.identifier == "ShowImagesSeque", let destination = segue.destination as? ViewImages, let itemIndex = tableView.indexPathForSelectedRow?.row {
let selectedItem = self.currentItemsArray[itemIndex]
destination.itemId = selectedItem.itemID
}
Here is the ViewImages class. In the story board, the class is there for the ViewController. When I print out itemId, its nil. Why?
class ViewImages: UIViewController {
...
weak var mDelegate:MyProtocol?
var itemId: String!
...
}
In HomeViewController, I have 5 static collection view cells:
- Alkalinity, Calcium, Magnesium, Nitrate, Phosphate
When a user taps a cell, it navigates to Details View Controller, into a variable called var dataReceived: Parameter?
Parameter model is used in HomeViewController to define the product name.
What I need to do is:
- If alkalinity cell is tapped, VC B should have an array of Alkalinity Product ex: alk 1, alk2, alk3
If a user taps calcium the array should be cal1 cal2 cal 3 and so on...
I tried using an enum and a switch statement.
MODEL
class Parameter {
var name: String
init(name: String) {
self.name = name
}
}
class Product {
var element: Element?
var name: [String]
init(element: Element?, name: [String]) {
self.element = element
self.name = name
}
}
enum Element {
case alkalinity
case calcium
case magnesium
}
VCB START HERE
class DetailViewController: UITableViewController {
var dataReceived : Parameter? // use this to get arrays the change
var alkalinityProducts = ["Al1", "a2", "a3", "a4", "a5", "a6"]
override func viewDidLoad() {
super.viewDidLoad()
self.title = dataReceived?.name
}
}
I need to find out how to update this, am I wrong with how I Modeled the data?
My Code produces a a tuples that is displayed on a label on view controller 1. I tried struct the label from vc1 to vc2 but the order is not being kept. All I want to do is replicate the exact order and the way the tuple is displayed on vc 1, on VC 2.
VIEW CONTROLLER 1
import UIKit
var number = [Int]()
var yourArray = [String]()
class ViewController: UIViewController {
#IBOutlet var labez: UILabel!
#IBOutlet var textA: UITextField!
#IBOutlet var textB: UITextField!
#IBAction func move(_ sender: Any) {
bad.mm = [String( labez.text ?? "")]
}
#IBAction func store(_ sender: Any) {
yourArray.append((textA.text!))
number.append(Int(textB.text!)!)
let tuples = zip(yourArray,number)
let sorted = tuples.sorted(by: { this, next in
if this.0 < next.0 {
return true
} else if this.0 == next.0 {
return this.1 < next.1
} else {
return false
}
})
print(sorted)
labez.text = sorted.map { " \($0)" }.joined(separator:"\n")
bad.mm = [String(describing: sorted.map { " \($0)" }.joined(separator:"\n")
)]
}
struct bad {
static var mm = [String]()
}
}
view controller 2
import UIKit
class ViewController2: UIViewController {
#IBOutlet var benCarson: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
benCarson.text = (String(describing: ViewController.bad.mm))
}
}
Besides my critique of how you designed your data and your naming conventions. I believe what you want is to ASSIGN your bad.mminstead of APPEND.
What is happening is the first time you enter values to bad.mm it is (a, 2). Then when it is appended you add the sorted arrays (a, 1), (a, 2) to the existing string, making it (a, 2), (a, 1), (a, 2) If you assign it it will be now just the new, sorted array, (a, 1), (a, 2).
To assign change
bad.mm.append(String(describing: sorted.map { " \($0)" }.joined(separator:"\n"))
In ViewController class to
bad.mm = String(describing: sorted.map { " \($0)" }.joined(separator:"\n")
In ViewController your move function does a similar thing where it APPENDS to bad.mm where you probably want to assign. However you assign it with a UITextField.Text property which is optional. Using the ?? operator you can give unwrap this optional while providing it a default value. An empty string is often a good default value. So for this I would suggest changing the line inside of #IBAction func move to the following:
bad.mm = labez.text ?? ""
Or just actually delete this line since you assign bad.mm and labez.text at the same time in your earlier function. But that is why you were getting the optional() around your text.
That should give you your desired effect. The reason your "tuple" isn't being passed in the right order is that your not grabbing the sorted tuple from one VC to the next, you are grabbing an improperly formatted string from one VC to another. Consider passing the sorted tuple directly and then formatting the string separately in both ViewControllers to reduce confusion.
Why don't you use prepare(for segue: instead of a struct to pass the tuples.
You can do it like this:
In ViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// give your segue an id (in the interface builder)
if segue.identifier == "mySegue" ,
let nextScene = segue.destination as? ViewController2 {
nextScene.yourTuples = yourArray // or anything else
}
}
And in your ViewController2:
class ViewController2: UIViewController {
val yourTuples: [String]()!
#IBOutlet var benCarson: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
benCarson.text = (String(describing: ViewController.bad.mm))
}
}
I tried creating global variables and updating the information when the view is loaded but data isn't being rendered.
GLOBAL VARIABLES
var viewName:String = ""
var viewDuration:String = ""
var viewPeriod:String = ""
var viewMinAmp:String = ""
var viewMaxAmp:String = ""
var viewStep:String = ""
var viewType:String = ""
Is there a more efficient way of passing information other than having global variables?
#IBOutlet var txtName: UITextField!
#IBOutlet var txtDuration: UITextField!
#IBOutlet var txtPeriod: UITextField!
#IBOutlet var txtMinAmp: UITextField!
#IBOutlet var txtMaxAmp: UITextField!
#IBOutlet var txtStep: UITextField!
#IBOutlet var txtType: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setInfo(viewName, duration: viewDuration, period: viewPeriod, minAmp: viewMinAmp, maxAmp: viewMaxAmp, step: viewStep, type: viewType)
}
func setInfo(name: String, duration: String, period: String, minAmp: String, maxAmp: String, step: String, type: String) {
txtName.text = name
txtDuration.text = duration
txtPeriod.text = period
txtMinAmp.text = minAmp
txtMaxAmp.text = maxAmp
txtStep.text = step
txtType.text = type
}
One solution would be to override prepareForSegue(segue:sender:) from within the view controller which contains the data that you wish to pass to the destination view controller.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "YourSegueName") {
//get a reference to the destination view controller
let destinationVC:ViewControllerClass = segue.destinationViewController as! ViewControllerClass
//set properties on the destination view controller
destinationVC.name = viewName
//etc...
}
}
For Swift 3.0
final class Shared {
static let shared = Shared() //lazy init, and it only runs once
var stringValue : String!
var boolValue : Bool!
}
To set stringValue
Shared.shared.stringValue = "Hi there"
to get stringValue
if let value = Shared.shared.stringValue {
print(value)
}
For Swift version below 3.0
You can pass data between views using singleton class. It is easy and efficient way. Here is my class ShareData.swift
import Foundation
class ShareData {
class var sharedInstance: ShareData {
struct Static {
static var instance: ShareData?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = ShareData()
}
return Static.instance!
}
var someString : String! //Some String
var selectedTheme : AnyObject! //Some Object
var someBoolValue : Bool!
}
Now in my ViewControllerOne I can set above variable.
//Declare Class Variable
let shareData = ShareData.sharedInstance
override func viewDidLoad() {
self.shareData.someString ="Some String Value"
}
And in my ViewControllerTwo I can access someString as
let shareData = ShareData.sharedInstance
override func viewDidLoad() {
NSLog(self.sharedData.someString) // It will print Some String Value
}
Personally, I prefer ways as follow:
If you want to jump forward between two view controllers (from A to B), as -pushViewController:animated: in navigation, you can define a property of model for Controller B and expose it publicly, then set this property explicitly before jumping from Controller A, it's pretty straightforward;
In case you want to jump backward from Controller B to A, use Delegate+Protocol mode. Controller B drafts a public protocol and own a "delegate" property, any object who would like to be the delegate of Controller B shall comply and implement its protocol(optionally). then prior to the jumping-backward, Controller B makes its delegate perform certain action(s) listed in protocol, the data could be transferred in this way;
Under certain circumstance, you may want to transfer data from a Controller(or controllers) to other multiple Controllers, use Notification mechanism if this is the case.
Apple has detailed instructions about delegate mode, notification mode in official documentation, check them out in XCode, :)
Just need to follow 3 steps, let's assume you want to pass data from ViewControllerA to ViewControllerB:
create a segue between ViewControllerA and ViewControllerB
name the segue with a Identifier in the attributes inspector of it
override the prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) at ViewControllerA
For step#3,:
if you are not using swift 2.1, please follow #Scott Mielcarski 's answer at this question
for people who are using swift 2.1, or who get error "Cannot convert value of type 'UIViewController' to specified type 'your view Controller class name', After following #Scott Mielcarski 's answer at this question, Please use:let destinationVC:ViewControllerClass = segue.destinationViewController as! ViewControllerClass instead of
let destinationVC:ViewControllerClass = segue.destinationViewController
This is tested on Swift 2.1.1 and it works for me.
If you don't actually want to pass data between view controllers but rather simply want to store a global variable you can do this:
This gives a great explanation for how to do this in Swift 5: https://www.hackingwithswift.com/example-code/system/how-to-save-user-settings-using-userdefaults
Summary:
To set a value:
let defaults = UserDefaults.standard
defaults.set("value", forKey: "key")
To get a String value:
let key = defaults.object(forKey: "StringKey") as? [String] ?? [String]()
To get integer value:
let key = defaults.integer(forKey: "IntegerKey")