Swift 4 Implementing drag & drop in collectionView with Cells - ios

I have custom classes which create an array of objects that are then turned into cells dynamically for my UICollectionView, and the classes all essentially look like this:
import Foundation
class BoardNote : NSObject {
var note_id : String = ""
var itemType : String = ""
var added_by : Any = ""
var link : Any = ""
var content : String = ""
var board_id : Any = ""
var date_added : Any = ""
}
An instance of this class is then used to create a cell like so:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier:
"noteViewCell", for: indexPath) as! NoteViewCell
cell.content.text = (itemArray[indexPath.row] as!
BoardNote).content
cell.noteId = (itemArray[indexPath.row] as! BoardNote).note_id
print("made note cell")
return cell
When I try to implement basic drag & drop in my collection view, I get the error "Could not cast value of type "BoardNote" to 'NSObject'.
I see that I might need to also make this an extension of class NSItemProviderWriting but I am not sure how.
Here is the beginning of my DragDelegate extension where the error is occurring on line 4, which is a Thread 1: signal SIGABRT:
extension BoardViewController : UICollectionViewDragDelegate
{
func collectionView(_ collectionView: UICollectionView,
itemsForBeginning session: UIDragSession, at indexPath: IndexPath) ->
[UIDragItem]
{
let item = self.itemArray[indexPath.row]
let itemProvider = NSItemProvider(object: item as! NSObject as!
NSItemProviderWriting)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}
Thanks!

First, you need to delete the item cast on this line:
NSItemProvider(object: item as! NSObject as! NSItemProviderWriting)
Make it just:
NSItemProvider(object: item)
Then, the error is self-explained: You need to make you custom class to conform NSItemProviderWriting and NSItemProviderReading protocol, then add the stub methods required from the protocols:
class BoardNote : NSItemProviderWriting, NSItemProviderReading{
var note_id : String = ""
var itemType : String = ""
var added_by : Any = ""
var link : Any = ""
var content : String = ""
var board_id : Any = ""
var date_added : Any = ""
}
NOTE: you can omit NSObject inheritance, here is why
You can add protocol stubs simply by clicking on error red signal then clicking "Fix".
Here a tutorial on how to do Drag and Drop, so you can check how to fill NSItemProviderWriting and NSItemProviderReading protocol stubs.
Here a similar question you can check
Basically, the question I linked was asking for a problem in your same line NSItemProvider(object: item), but the problem was the dev was not giving the method a class instance but the class name

Related

swift table view cells does not let me use indexPath.row

i am trying to populate data into cells inside of a table view. I created a chosenPlanData var which is initialized to an object with the data inside of it.. The object has properties such as "name" and "event location". An issue occurs when inside of 'cellForRowAt'. It does not let me add [indexPath.row] to the cell i am creating, which in turn does not populate the cells correctly.
For instance - i removed indexPath.row from the first cell.nameLbl.text call - and in turn every single name label in the table view was the same. here is piece of the code
var chosenPlanData = ChosenPlan()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "individualPlansCell") as? PlanitsHomeViewCell else { return UITableViewCell() }
cell.nameLbl.text = chosenPlanData.nameOfEvent[indexPath.row] // error Cannot assign value of type 'Character' to type 'String?'
cell.dateAndTimeLbl.text = chosenPlanData.eventStartsAt[indexPath.row] as? String // error 'subscript' is unavailable: cannot subscript String with an Int, see the documentation comment for discussion
cell.nameLbl.text = chosenPlanData.nameOfEvent // This works - but every single cell has the same nameLbl though
return cell
}
// Here is the call back where i initialize the value for chosenPlanData
let EventbriteTVC = segue.destination as! EventbriteTableView
EventbriteTVC.callbackChosePlan = { result in
DispatchQueue.main.async {
self.individualPlanitsTableView.reloadData()
}
self.chosenPlanData = result
}
import Foundation
import UIKit
class ChosenPlan {
var nameOfEvent : String = ""
var eventStartsAt : String = ""
var eventLocationIs : String = ""
var eventURL : String = ""
var imageForPlan : String?
convenience init( eventName: String, eventTime: String, eventLocation: String, eventImage: String){
self.init()
self.nameOfEvent = eventName
self.eventStartsAt = eventTime
self.eventLocationIs = eventLocation
//self.eventURL = eventLink
self.imageForPlan = eventImage
//eventLink: String,
}
}
Your chosenPlanData variable is a single instance of ChosenPlan - You cannot subscript a single instance.
It needs to be an array of ChosenPlan:
var chosenPlanData = [ChosenPlan]()
Then you can index into this array:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "individualPlansCell") as? PlanitsHomeViewCell else { return UITableViewCell() }
cell.nameLbl.text = chosenPlanData[indexPath.row].nameOfEvent
cell.dateAndTimeLbl.text = chosenPlanData[indexPath.row].eventStartsAt
extending my comment
var chosenPlanData = ChosenPlan()
chosenPlanData is object of ChosenPlan
Now in cellForRow you writing chosenPlanData.nameOfEvent[indexPath.row] but nameOfEvent is String as per your ChosenPlan that you mentioned in question.
For more info,
chosenPlanData.nameOfEvent[indexPath.row] this line represents you using the n th (indexPath.row) object of nameOfEvent which is object of chosenPlanData
Hope now will be more cleared.
Solution
var chosenPlanData = [ChosenPlan]() <- create array
In cellForRow chosenPlanData[indexPath.row].nameOfEvent that means you'r using nameOfEvent of nth object of chosenPlanData.
Type handling is very important in Swift. Try this.
cell.nameLbl.text = String(chosenPlanData.nameOfEvent[indexPath.row])
let index = chosenPlanData.eventStartsAt.index(chosenPlanData.eventStartsAt.startIndex, offsetBy: 3)
cell.dateAndTimeLbl.text = String(chosenPlanData.eventStartsAt[index])
if
var chosenPlanData = [ChosenPlan]()
Try this:-
cell.nameLbl.text = "\(chosenPlanData[indexPath.row].nameOfEvent)"
or
cell.nameLbl.text = "\(chosenPlanData[indexPath.row].nameOfEvent ?? "")"

How to find a particular key value with name in swift 3?

Here I am having Json data which I am retrieving from model class in which I need to find if the key value pair title having word starting with Discount for example if I find key value pair starting with Discount I need to set the particular table view cell label as Discount how to find it from key value pairs can anyone help me how to implement this ?
Here is my model class data
struct TotalPriceSegments {
let code : String?
let title : String?
let value : Double?
init(json: [String:Any]) {
self.code = json["code"] as? String
self.title = json["title"] as? String
self.value = json["value"] as? Double
}
}
Here is table view code
let cell = tableView.dequeueReusableCell(withIdentifier: "checkout", for: indexPath) as! CheckoutTableViewCell
let array = totalPriceModel?.totalSegments[indexPath.row]
cell.titleLabel.text = array?.title
let total = doubleToStringDecimalPlacesWithDouble(number: Double((array?.value)!), numberOfDecimalPlaces: 2)
cell.valueLabel.text = (String(describing:("$ \(total)")))
return cell
In cellForRow method add such check:
if array?.title.hasPrefix("Discount") {
cell.titleLabel.text = "Discount"
}

Using Swift 3, how can I parse a JSON file so that I can access the data globally?

I'm a newbie to iOS development, so apologies in advance if I use incorrect terminology. I am working on a basic app for a school project and would appreciate any help!
I have a JSON file with 20 different objects (please see an example of the first object below), which I would like to parse and access in three different View Controllers for different purposes - the same data, just ordered or presented in different ways.
The JSON file is stored locally within the xCode project. How can I parse it so that I may access the data globally within different View Controllers?
My understanding is that it would be best to parse the JSON file in the AppDelegate controller and then call the data within each individual ViewController but I am unsure of how to do this programatically.
The name of the JSON file is "locations.json"
{
locations: [
{
"id": 0001,
"name": "Helensburgh Tunnels",
"type": ["Tunnels", "Beach", "Views"],
"location": "Helensburgh, South Coast",
"image": "Helensburgh-Tunnels.jpg",
"activity": "Walking, photography, tunnelling",
"isVisited": false,
"latitude": -34.178985,
"longitude": 150.992867
}
}
Then I would appreciate if you could expand upon how I may read this data in a TableView. My TableView cells are currently configured as below (reading out of an array which is hard coded into the same ViewController) - how can I update this to read the parsed JSON data?
Name of ViewController; LocationTableViewController.swift
var locations:[Location] = [
Location(name: "Helensburgh Tunnels", type: "Old, abandoned train tunnels.", location: "Helensburgh, South Coast", image: "Helensburgh-Tunnels.jpg", activity: "Walking, photography, tunnelling", isVisited: false, latitude: "-34.178985", longitude: "150.992867")
]
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "cell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! LocationTableViewCell
// Configure the cell
cell.nameLabel.text = locations[indexPath.row].name
cell.thumbnailImageView.image = UIImage(named: locations[indexPath.row].image)
cell.locationLabel.text = locations[indexPath.row].location
cell.typeLabel.text = locations[indexPath.row].type
cell.accessoryType = locations[indexPath.row].isVisited ? .checkmark : .none
return cell
}
The location model is as follows.
Name of ViewController; Location.swift
class Location: NSObject, MKAnnotation {
var name: String = ""
var type: String = ""
var location: String = ""
var image: String = ""
var activity: String = ""
var isVisited: Bool = false
var rating: String = ""
var latitude: Double = 0.0
var longitude: Double = 0.0
init(name: String, type: String, location: String, image: String, activity: String, isVisited: Bool, latitude: String, longitude: String) {
self.name = name
self.type = type
self.location = location
self.image = image
self.activity = activity
self.isVisited = isVisited
self.latitude = Double(latitude)!
self.longitude = Double(longitude)!
}
public var coordinate: CLLocationCoordinate2D { get {
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
return coordinate
}
}
}
Create a new Swift File like GlobalClass.Swift
import Foundation and UIKit in GlobalClass
make function to read location like this:-
func readLocation() ->[String:Any]?{
do {
if let file = Bundle.main.url(forResource: "locations", withExtension: "json") {
let data = try Data(contentsOf: file)
let json = try JSONSerialization.jsonObject(with: data, options: [.mutableContainers])
if let object = json as? [String: Any] {
return object
}
return nil
}
} catch {
print(error.localizedDescription)
}
return nil
}
and call this function from TableView like this:-
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "cell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! LocationTableViewCell
// Configure the cell
if let locationJson = readLocation(){
if let locations = locationJson["location"] as? [[String:Any]]{
cell.nameLabel.text = locations[indexPath.row]["name"] as! String
cell.thumbnailImageView.image = UIImage(named: (locations[indexPath.row]["image"] as! String))
cell.locationLabel.text = locations[indexPath.row]["location"] as! String
cell.typeLabel.text = locations[indexPath.row]["type"] as! String
cell.accessoryType = (locations[indexPath.row]["isVisited"] as! Bool) ? .checkmark : .none
}
}
return cell
}
and number of rows in section should be
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let locationJson = readLocation(){
if let locations = locationJson["location"] as? [[String:Any]]{
return locations.count
}
}
return 0
}
Make sure your location file should be in correct json format otherwise this function throw an error
I wouldn't normally look at the AppDelegate for this type of thing. You should do as little there as possible,
You may want to look at an MVVM-like approach. You could create a viewmodel whose job is to vend out the various permutations of the data. You could create this as a singleton, but if your app isn't too complex you could simply instantiate the viewmodel in each of your view controllers.
I generally write a data manager which is responsible for parsing the data and provides a method to retrieve the data as an array of models. The viewmodel can call the datamanager method when it is instantiated or as needed. Again, for simple projects you could do the data parsing directly in the viewmodel. The viewmodel would have various methods that the view controllers would use to retrieve the data in the necessary format.
The above is heavily simplified, but should work for your needs.
Basic flow:
View controller(s) instantiates the viewmodel (or gets a shared instance) and calls a method on the viewmodel
Viewmodel calls method on datamanager if it doesn't already have data.
Datamanager parses JSON and returns array of models
Viewmodel method manipulates data from models (e.g. sorting) and returns them to the view controller.
View controller presents the returned data.
Read content from file & create a dictionary
let urlString = Bundle.main.path(forResource: "filename", ofType: "txt")
let url = URL(string: urlString!)
if let url = url {
do {
let data = try Data(contentsOf: url)
let jsonDictionary = try JSONSerialization.jsonObject(with: data, options:[])
// Model creation code here.
} catch {
print("Error : ")
}
}
From jsonDictionary object you construct your model classes. Please refer the link for, How to parse the json objects and store it in model classes.
Model :
struct LocationDetails {
var id: Int = 0
var name: String = ""
// Other variables are here
}
Manager Class :
// Here you can store common properties.
class AppUtils {
class var parsedResponse: [LocationDetails]
}
After constructed the models list. For example [LocationDetails]. You have to store it in the AppUtils class parsedResponse variable.
var locationDetailsList = [LocationDetails]()
//Your model construction code here.
//Store the array of objects into AppUtils class
AppUtils.parsedResponse = locationDetailsList
Access the models on different UIViewController like this.
AppUtils.parsedResponse
NOTE : If your app is not accessing the data then don't keep the values always on memory. Try to clean up the memory whenever possible.

How to Unwrap a Modal class object in Swift 3

Model Class:
class CountryModel: NSObject {
var name:NSString!
var countryId:NSString!
init(name: NSString, countryId: NSString) {
self.name = name
self.countryId = countryId
}
}
ViewController:
var nameArr = [CountryModel]() // Model Class object
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
let arr = nameArr[indexPath.row] // How to do if let here
let str:String? = "\(arr.countryId) \(arr.name)"
if let sstr = str{
cell.textLabel?.text = sstr
}
return cell
}
How should one unwrap this because output is an optional, if I try to unwrap nameArr[indexPath.row] gives an error initializer for conditional binding must have optional type, not "country modal"
It works fine I am not concatenating arr.countryId with arr.name
This works fine
var nameArr = [CountryModal]()
nameArr.append(CountryModal.init(name: "harshal", countryId: "india"))
nameArr.append(CountryModal.init(name: "james", countryId: "australia"))
let arr = nameArr[0]
let str:String? = "\(arr.countryId!) \(arr.name!)"
if let sstr = str{
print(sstr)
}
let arr2 = nameArr[1]
let str2:String? = "\(arr2.countryId!) \(arr2.name!)"
if let sstr2 = str2{
print(sstr2)
}
You can try this library https://github.com/T-Pham/NoOptionalInterpolation. It does exactly that
import NoOptionalInterpolation
let n: Int? = 1
let t: String? = nil
let s: String? = "string1"
let o: String?? = "string2"
let i = "\(n) \(t) \(s) \(o)"
print(i) // 1 string1 string2
NoOptionalInterpolation gets rid of "Optional(...)" and "nil" in Swift's string interpolation. This library ensures that the text you set never ever includes that annoying additional "Optional(...)". You can also revert to the default behaviour when needed.
Also, please note that this does not affect the print function. Hence, print(o) (as opposed to print("(o)"), o as in the example above) would still print out Optional(Optional("string2"))
Avoid forced unwrapping as much as possible. i.e. using '!'. Whenever you see !, try and think of it as code smell.
For json parsing you would not know for sure if the values for countryId and name exists or not. So, it makes sense to keep them as optionals and gracefully try to unwrap them later.
class CountryModel {
var name: String?
var countryId: String?
init(name: String?, countryId: String?) {
self.name = name
self.countryId = countryId
}
}
var nameArr = [CountryModel]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
fatalError("cell should always be initialised")
}
var str: String?
let arr = nameArr[indexPath.row] // How to do if let here
if let countryId = arr.countryId, let name = arr.name {
str = "\(countryId) \(name)"
}
if let unwrappedString = str {
cell.textLabel?.text = unwrappedString
}
return cell
}
Bonus:
You could simplify your json parsing a lot using the Object Mapper library. It simplifies a lot of your parsing logic.

Using Array of Dictionary to populate tableview in Swift

I have an array of dictionary created as below:
var menuItems = [["Image" : "bars_icon_main_page", "Title" : "Bars"], ["Image" : "clubs_icon_main_page", "Title" : "Clubs"]]
I've created a custom table view cell and populating the table view cell as below:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: MenuTableViewCell = tableView.dequeueReusableCellWithIdentifier("MenuCell") as MenuTableViewCell
let dict = menuItems[indexPath.row]
cell.menuImage.image = UIImage(named: dict["Image"]!)
cell.menuTitle.text = dict["Title"]
return cell
}
When debugging the src code, after executing below line, "dict" is nil.
let dict = menuItems[indexPath.row]
I am not able to make out what's the issue.
The following line of code is looking for a key of Image:
cell.menuImage.image = UIImage(named: dict["Image"]!)
But menuItems is using Image: as the key:
var menuItems = [["Image:" : "bars_icon_main_page", "Title" : "Bars"], ["Image:" : "clubs_icon_main_page", "Title" : "Clubs"]]
I suspect you did not intend to include the colon in the key:
var menuItems = [["Image" : "bars_icon_main_page", "Title" : "Bars"], ["Image" : "clubs_icon_main_page", "Title" : "Clubs"]]
Your array of Dictionary is implementing with Image: key and your code is using Image without : in key

Resources