I get the following error when trying to pass data from one viewcontroller to another: "Cannot assign value of type 'activityTableViewController.request' to type 'activityDetailTableViewController.request?'"
What am I doing wrong?
First view controller:
class activityTableViewController: UITableViewController {
struct request {
var fromDateAndTime: String
var toDateAndTime: String
var createdBy: String
init(fromDateAndTime: String, toDateAndTime: String, createdBy: String) {
self.fromDateAndTime = fromDateAndTime
self.toDateAndTime = toDateAndTime
self.createdBy = createdBy
}
}
var requestList: [request] = []
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "activityToDetail" {
if let nextViewController = segue.destination as? activityDetailTableViewController {
let indexPath = tableView.indexPathForSelectedRow
nextViewController.requestDetail = requestList[indexPath!.row]
}
}
}
}
Second view controller:
class activityDetailTableViewController: UITableViewController {
struct request {
var fromDateAndTime: String
var toDateAndTime: String
var createdBy: String
init(fromDateAndTime: String, toDateAndTime: String, createdBy: String) {
self.fromDateAndTime = fromDateAndTime
self.toDateAndTime = toDateAndTime
self.createdBy = createdBy
}
}
var requestList: request!
}
First of all please conform to the naming convention that class and struct names start with a capital letter.
Both structs seem to be the same but they are different objects because they are declared in different name spaces.
Create one struct outside of any class and delete the initializer because you get it for free.
struct Request {
var fromDateAndTime: String
var toDateAndTime: String
var createdBy: String
}
You need to only have 1 struct as it will be visible inside all the app , and get it out of any class
struct Request {
var fromDateAndTime,toDateAndTime,createdBy: String
}
plus there is no need to write init inside a struct type , and start it with capital letter
There is no availability to declare a struct with the same name as another it's a language logical constraint
Related
Is there any way to assign realm result object to variable with a class type: I have var movies: Results! in one view controller and variable var currentMovie : Movie? in another and I'm trying to pass it thru prepare for segue method.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destVc = segue.destination as? PlaySelectedFavoritesViewController
destVc?.movies = movies[selectedRow]
}
But, that results in the following error.
Error: Cannot assign value of type 'FavoriteMoviesModel' to type
'Movie?'
Here is class and struct
class FavoriteMoviesModel: Object {
#objc dynamic var title = ""
#objc dynamic var poster_path = ""
#objc dynamic var id = 0
}
```
Movie struct
```
struct Movie: Codable {
let title: String?
let poster_path: String?
let id: Int?
enum CodingKeys: String, CodingKey {
case title = "title"
case poster_path = "poster_path"
case id = "id"
}
}
Data keys change for different types and I want to use just this tableview cell for different types. I don't want to create new tableview cell for each type. Is it possible? I guess I should use generics but how can I implement for this problem?
I have a custom UITableViewCell that includes
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var departmentLabel: UILabel!
#IBOutlet weak var genderLabel: UILabel!
{
"data": [
{
"type": "employee",
"data": {
"name": "Michael",
"department": "HR",
"gender": "Male"
}
},
{
"type": "employer",
"data": {
"name": "Julia",
"division": "Finance",
"sex": "Female"
}
}
]
}
If I'm understanding your question, you want data that takes different forms but returned in the same array to be representable in a single table view cell. This is definitely doable, the challenge being parsing the variable JSON response into something consistent that you can send to your cells to be displayed.
This sort of thing would be possible using a custom implmentation of Codable. The caveat being that you'll need to know what sort of options this data could be coming down in (ie will gender always be either 'sex' or 'gender'?)
An example would be:
struct CompanyPersonContainer: Decodable {
var data: [CompanyRelatedPerson]
enum CodingKeys: String, CodingKey {
case data
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
data = []
if var dataContainer = try? values.nestedUnkeyedContainer(forKey: CodingKeys.data) {
while !(dataContainer.isAtEnd) {
let companyPerson: CompanyRelatedPerson
if let employee = try dataContainer.decodeIfPresent(Employee.self) {
companyPerson = employee
} else if let employer = try dataContainer.decodeIfPresent(Employer.self) {
companyPerson = employer
} else {
fatalError() // You need to know the total possible values you'll be decoding
}
data.append(companyPerson)
}
}
}
}
protocol CompanyRelatedPerson: Codable {
var name: String { get set }
var department: String { get set }
var gender: String { get set }
}
struct Employee: Codable, CompanyRelatedPerson {
var name: String
var department: String
var gender: String
}
struct Employer: Codable, CompanyRelatedPerson {
var name: String
var department: String
var gender: String
enum CodingKeys: String, CodingKey {
case name
case department = "division"
case gender = "sex"
}
}
I think you can create a protocol ViewModel then create different view models who inherit this protocol.
protocol ViewModel {
var name: String { get set }
var department: String { get set }
var gender: String { get set }
}
struct EmployeeViewModel: ViewModel {
var name: String
var department: String
var gender: String
// Suppose you already have this data model from data.
init(employee: Employee) {
self.name = employee.name
self.department = employee.department
self.gender = employee.gender
}
}
struct Employer: ViewModel {
var name: String
var department: String
var gender: String
init(employer: Employer) {
self.name = employer.name
self.department = employer.division
self.gender = employer.sex
}
}
Then, create a function in your tableview cell to assign the values to your properties.
// In your table view cell
func setViewModel(_ viewModel: ViewModel) {
self.nameLabel.text = vm.name
// do the same for the rest of labels...
}
For me, the advantage of using a protocol view model is that you can pass any view models inherited from protocol to your view to assign the value, that avoids to expose your data in your view (tableview cell in this case).
I have a custom class called Message.
import UIKit
class Message {
var sender: String
var message: String
init?(sender: String, message: String) {
self.sender = sender
self.message = message
}
}
I also have a custom class called Chat that has a variable, called messageList, that is an array of the Message class.
class Chat {
//MARK: Properties
var name: String
var image: UIImage?
var animal: String
var messageList = [Message]()
//MARK: Initialisation
init?(name: String, image: UIImage?, animal: String) {
if name.isEmpty || animal.isEmpty {
return nil
}
self.name = name
self.image = image
self.animal = animal
let firstMessage = Message(sender: "animal", message: "Hi! Nice meeting you!")
self.messageList.append(firstMessage)
}
}
I have tried many different ways, but each time, I get an error when messageList is declared saying the following: "Property cannot be marked #NSManaged because its type cannot be represented in Objective-C" or "Property cannot be declared public because its type uses an internal type".
Thank you in advance,
Alex
Classes are declared as internal by default, so you have to add the public keyword to make them public.
import UIKit
public class Message {
var sender: String
var message: String
init?(sender: String, message: String) {
self.sender = sender
self.message = message
}
}
I'm trying to use a Struct to pass some variables that I get from a Facebook Graph Request such as email, name, gender, etc.
I've created the Struct ('fbDemographics') and the variables in 'ViewController' but I get an error when I try to call the struct and one of the variables in 'SecondViewController' (Type 'ViewController' has no member 'fbDemographics'). I've never used struct before so a bit baffled why I am getting this error. Thanks for any thoughts. The code for both view controllers is below:
class ViewController: UIViewController, FBSDKLoginButtonDelegate {
override func viewDidLoad() {
super.viewDidLoad()
struct fbDemographics {
static var relationship_status: String?
static var gender: String?
static var user_education_history: String?
static var user_location: String?
static var email: String?
static var name: String?
}
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, relationship_status, gender, user_location, user_education_history, email"]).start(completionHandler: { (connection, result, error) -> Void in
if (error == nil){
//let fbDetails = result as! NSDictionary
//print(fbDetails)
if let userDataDict = result as? NSDictionary {
fbDemographics.gender = userDataDict["gender"] as? String
fbDemographics.email = userDataDict["email"] as? String
fbDemographics.name = userDataDict["name"] as? String
fbDemographics.user_location = userDataDict["user_location"] as? String
fbDemographics.user_education_history = userDataDict["user_education_history"] as? String
fbDemographics.relationship_status = userDataDict["relationship_status"] as? String
let myEducation = fbDemographics.user_education_history
let myEmail = fbDemographics.email
let myGender = fbDemographics.gender
let myName = fbDemographics.name
let myStatus = fbDemographics.relationship_status
let myLocation = fbDemographics.user_location
self.performSegue(withIdentifier: "LoginToHome", sender: (Any).self)
}
}
SECOND VIEW CONTROLLER
class SecondViewController: UIViewController {
#IBAction func verticalSliderChanged(_ sender: UISlider) {
let currentValue = String(sender.value);
sliderLabel.text = "\(currentValue)"
func viewDidLoad() {
super.viewDidLoad()
***ViewController.fbDemographics.myEmail***
}
}
The problem is that you've defined your struct inside viewDidLoad. So the scope is limited to that method. Move it out of viewDidLoad, but still in ViewController, and you should be able to access it from the SecondViewController. Obviously, you'll have to fix that reference to myEmail, too, because it's called email. Also, in your SecondViewController you should pull viewDidLoad implementation out of the verticalSliderChanged method; the viewDidLoad should be a top-level instance method of SecondViewController, not defined inside another method.
There are deeper problems here, though. Rather than using struct with static variables, you really should make those simple instance variables, create an instance of your FbDemographics type (note, start struct types with uppercase letter), and then pass this instance in prepare(for:sender:).
For example, the right way to pass data would be to:
eliminate static variables;
give your struct a name that starts with uppercase letter;
create instance of your struct; and
pass this instance to the destination view controller in prepare(for:sender:).
E.g.
struct FbDemographics {
var relationshipStatus: String?
var gender: String?
var userEducationHistory: String?
var userLocation: String?
var email: String?
var name: String?
}
class ViewController: UIViewController {
var demographics: FbDemographics?
override func viewDidLoad() {
super.viewDidLoad()
performRequest() // I actually don't think you should be initiating this in `viewDidLoad` ... perhaps in `viewDidAppear`
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? SecondViewController {
destination.demographics = demographics
}
}
func performRequest() {
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, relationship_status, gender, user_location, user_education_history, email"]).start { connection, result, error in
guard let userDataDict = result as? NSDictionary, error == nil else {
print("\(error)")
return
}
self.demographics = FbDemographics(
relationshipStatus: userDataDict["relationship_status"] as? String,
gender: userDataDict["gender"] as? String,
userEducationHistory: userDataDict["user_education_history"] as? String,
userLocation: userDataDict["user_location"] as? String,
email: userDataDict["email"] as? String,
name: userDataDict["name"] as? String
)
self.performSegue(withIdentifier: "LoginToHome", sender: self)
}
}
}
And then SecondViewController could:
class SecondViewController: UIViewController {
var demographics: FbDemographics!
override func viewDidLoad() {
super.viewDidLoad()
let value = demographics.email // this should work fine here
}
}
Supposing I have a UICollectionViewCell and a UITableViewCell with identical properties. Rather than have two functions which populate those cells, could I have a generic that takes something , determine what that was and then cast it to the correct thing to perform actions on it before returning?
my thinking is:
func setUpCell<T>(event: Event, cell:T) -> T {
// figure out what T is and cast it
cell.event.bar = event.bar
return cell
}
is this a good way of avoiding large amounts of code duplication?
Given your model type
struct Event {
let title: String
let desc: String
}
define this protocol
protocol EventCell: class {
var id: String? { get set }
var desc: String? { get set }
}
Now conform your UITabelViewCell and UICollectionViewCell to it
class TableCell: UITableViewController, EventCell {
var id: String?
var desc: String?
}
class CollectionCell: UICollectionViewCell, EventCell {
var id: String?
var desc: String?
}
And finally define this extension
extension EventCell {
func populate(event:Event) {
self.id = event.id
self.desc = event.desc
}
}
That's it. Now both your cells (UITabelViewCell and UICollectionViewCell) have the populate method!
Does this match what you were thinking?
import UIKit
struct Event {
var bar:Int = 0
}
// Protocol to group common additions
protocol ViewCellAdditions {
init()
var separatorInset:Int { get set }
var event:Event { get set}
}
// Generic function to work on any class that adopts ViewCellAdditions
func setUpCell<T: ViewCellAdditions>(event: Event, cell:T, foo:Int) -> T {
var newCell = T()
newCell.separatorInset = foo
newCell.event.bar = event.bar
return newCell
}
// Class that adopts ViewCellAdditions
class NewCellClass: ViewCellAdditions {
required init() {}
var separatorInset:Int = 10
var event:Event = Event()
}
// How to use it
let aCell = NewCellClass()
let aEvent = Event()
let newCell = setUpCell(aEvent, cell: aCell, foo: 5)