I have a table that is populated by a search function. There are two buttons within the cell, a checkmark to say yes to a user and an X to say no. There is an insert function that inserts the selection into the database. Unfortunately the value from the table is not being passed to the insert function. Within the insert function, I'm using guestusername.text which is the name of the label in my cell. I'm getting the error 'Use of unresolved identifier guestusername'. I've tried everything I can think of, code below.
class MyShotsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var guest = [AnyObject]()
var avas = [UIImage]()
var valueToPass:String!
var revieweduser:String!
var age = [AnyObject]()
var city = [AnyObject]()
var state = [AnyObject]()
#IBOutlet var tableView: UITableView!
var cell: MyShotsCell?
var index = 0
override func viewDidLoad() {
super.viewDidLoad()
doSearch("")
}
// cell numb
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return guest.count
}
// cell config
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyShotsCell
// get one by one user related inf from users var
let guest2 = guest[indexPath.row]
let ava = avas[indexPath.row]
// shortcuts
let guestname = guest2["username"] as? AnyObject
let age = guest2["age"]
let city = guest2["city"] as? String
let state = guest2["state"] as? String
// refer str to cell obj
cell.guestusername.text = guestname as! String
cell.ageLbl.text = (NSString(format: "%#", age as! CVarArg) as String)
cell.cityLbl.text = city
cell.stateLbl.text = state
cell.avaImg.image = ava as? UIImage
return cell
}
// search / retrieve users
public func doSearch(_ guestusername : String) {
// shortcuts
let username = user?["username"] as! String
let url = URL(string: "http://www.xxxxx.com/xxxxx.php")!
var request = URLRequest(url: url) // create request to work with users.php file
request.httpMethod = "POST" // method of passing inf to users.php
let body = "revieweduser=\(username)" // body that passes inf to users.php
request.httpBody = body.data(using: .utf8) // convert str to utf8 str - supports all languages
// launch session
URLSession.shared.dataTask(with: request) { data, response, error in
// getting main queue of proceeding inf to communicate back, in another way it will do it in background
// and user will no see changes :)
DispatchQueue.main.async(execute: {
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
// clean up
self.guest.removeAll(keepingCapacity: false)
self.avas.removeAll(keepingCapacity: false)
self.tableView.reloadData()
// delcare new secure var to store json
guard let parseJSON = json else {
print("Error while parsing")
return
}
guard let parseUSERS = parseJSON["users"] else {
print(parseJSON["message"] ?? [NSDictionary]())
return
}
self.guest = parseUSERS as! [AnyObject]
print(self.guest)
// for i=0; i < users.count; i++
for i in 0 ..< self.guest.count {
// getting path to ava file of user
let ava = self.guest[i]["ava"] as? String
let revieweduser = self.guest[i]["username"] as? String
let age = (NSString(format: "%#", self.guest[i]["age"] as! CVarArg) as String)
let city = self.guest[i]["city"] as? String
let state = self.guest[i]["state"] as? String
self.tableView.reloadData()
} catch {
DispatchQueue.main.async(execute: {
let message = "\(error)"
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
} else {
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
})
} .resume()
}
// custom body of HTTP request to upload image file
func createBodyWithParams(_ parameters: [String: String]?, boundary: String) -> Data {
let body = NSMutableData();
if parameters != nil {
for (key, value) in parameters! {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendString("\(value)\r\n")
}
}
return body as Data
}
func insertShot(_ rating : String) {
self.tableView.reloadData()
let reviewer = user?["username"] as! String
// url path to php file
let url = URL(string: "http://www.xxxxxx.com/xxxxxxx.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
// param to be passed to php file
let param = [
"user" : reviewer,
"revieweduser" : cell?.guestusername.text,
"rating" : rating
] as [String : Any]
// body
let boundary = "Boundary-\(UUID().uuidString)"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
// ... body
request.httpBody = createBodyWithParams(param as? [String : String], boundary: boundary)
// launch session
URLSession.shared.dataTask(with: request) { data, response, error in
// get main queu to communicate back to user
DispatchQueue.main.async(execute: {
if error == nil {
do {
// json containes $returnArray from php
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
// declare new var to store json inf
guard let parseJSON = json else {
print("Error while parsing")
return
}
// get message from $returnArray["message"]
let message = parseJSON["message"]
//print(message)
// if there is some message - post is made
if message != nil {
// reset UI
// self.msgTxt.text = ""
// switch to another scene
//self.tabBarController?.selectedIndex = 3
_ = self.navigationController?.popViewController(animated: true)
}
} catch {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = "\(error)"
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
} else {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
})
}.resume()
return
}
#IBAction func yesBtn_clicked(_ sender: UIButton) {
self.insertShot("Yes")
}
#IBAction func noBtn_clicked(_ sender: UIButton) {
self.insertShot("No")
}
}
Related
I'm trying to send data (first name, last name, age, image) to a MySQL database. And I can. The following is what I have.
class PostViewController: UIViewController {
#IBAction func selectTapped(_ sender: UIButton) {
postData()
}
func postData() {
var request = URLRequest(url: URL(string: "http://www.mywebsite.tv/post.php")!)
request.httpMethod = "POST"
let fName = firstField.text!
let lName = lastField.text!
let ageStr = ageField.text!
let image = imageView.image!
guard let pictStr = convertImageBase64(image: image) else {
return
}
let postString = "a=\(fName)&b=\(lName)&c=\(ageStr)&d=\(pictStr)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=\(String(describing: error))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(String(describing: responseString))")
}
task.resume()
}
func convertImageBase64(image: UIImage) -> String? {
guard let pictData = UIImagePNGRepresentation(image) else {
return nil
}
let strBase64: String = pictData.base64EncodedString(options: Data.Base64EncodingOptions.lineLength64Characters)
return strBase64
}
}
And populating UITableView with data from MySQL..
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, DataModelProtocol {
// MARK: - Variables
var myItems = NSArray()
// MARK: - IBOutlets
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// somehow loading data //
}
// MARK: - TableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ProfileTableViewCell
let item: PictureModel = myItems[indexPath.row] as! PictureModel
cell.firstLabel.text = item.fName
cell.lastLabel.text = item.lName
cell.ageLabel.text = item.ageStr
print(item.pictStr!) // returning iVBORw0KGgoAAAANSUh...
if let img = convertBase64Image(base64String: item.pictStr!) {
cell.pictureImageView.image = img
}
return cell
}
func convertBase64Image(base64String: String) -> UIImage? {
if let pictData = Data(base64Encoded: base64String, options: Data.Base64DecodingOptions.ignoreUnknownCharacters) {
return UIImage(data: pictData)
} else {
return nil
}
}
}
The thing is cell.pictureImageView.image is always nil. And I now know why. When I post a picture, a sample base64 string is
iVBORw0KGgoAAAANSUhEUgAAAFYAAACACAYAAACRMZ7FAAAAAXNSR0IArs4c6QAA\r\nABxp...
And the decoded string that I get is
iVBORw0KGgoAAAANSUhEUgAAAFYAAACACAYAAACRMZ7FAAAAAXNSR0IArs4c6QAA ABxp...
So the Data.Base64DecodingOptions.ignoreUnknownCharacters option actually replaces \r\n with a white space. How can I encode an image and then decode it back the same?
Drop the .lineLength64Characters options:
func convertImageBase64(image: UIImage) -> String? {
guard let pictData = UIImagePNGRepresentation(image) else {
return nil
}
let strBase64: String = pictData.base64EncodedString(options: [])
return strBase64
}
That way the string will not have a \r\n in it in the first place.
Question 1 :
I am using YouTube API to display a playlist of videos in a UITableView but it's not working. It's working fine when I make it for a single video, one video appears in the UITableView.
How can I display a playlist of any YouTube channel? I am using this code in my UITableView.
My UITableView code :
import UIKit
import AVFoundation
class YTViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, AVAudioPlayerDelegate {
#IBOutlet weak var txtSearch: UITextField!
#IBOutlet weak var searchResultTableView: UITableView!
// Set up a network session
let session = URLSession.shared
// ReST GET static String parts
let BASE_URL: String = "https://www.googleapis.com/youtube/v3/"
let SEARCH_VIDEO: String = "channels?part=snippet&q="
let VIDEO_TYPE: String = "&id=UCJIc9yX_3iHE2CfmUqoeJKQ&key="
let API_KEY: String = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
#IBAction func btnSearchClicked(_ sender: UIButton) {
}
func getVideoList() {
let methodArguments: [String: AnyObject] = [
"query": txtSearch.text! as AnyObject
]
// Format the search string (video title) for http request
let videoTitle: String = escapedParameters(methodArguments)
// Make the query url
// sample: https://www.googleapis.com/youtube/v3/search?part=snippet&q=werewolf&type=video&key=AIzaSyDDqTGpVR7jxeozoOEjH6SLaRdw0YY-HPQ
let searchVideoByTitle = BASE_URL + SEARCH_VIDEO + videoTitle + VIDEO_TYPE + API_KEY
print("#####################\(searchVideoByTitle)")
if let url = URL(string: searchVideoByTitle) {
let request = URLRequest(url: url)
// Initialise the task for getting the data
initialiseTaskForGettingData(request, element: "items")
}
}
// Array to store all the desired values dictionaries
var videosArray: Array<Dictionary<String, AnyObject>> = [[String: AnyObject]]()
func initialiseTaskForGettingData(_ request: URLRequest, element: String) {
// Initialize task for getting data
// Refer to http://www.appcoda.com/youtube-api-ios-tutorial/
let task = session.dataTask(with: request, completionHandler: {(data, HTTPStatusCode, error) in
// Handler in the case of an error
if error != nil {
print(error as Any)
return
}
else {
// Parse that data received from the service
let resultDict: [String: AnyObject]!
do {
// Convert the JSON data to a dictionary
resultDict = try JSONSerialization.jsonObject(with: data! as Data, options: .allowFragments) as! [String: AnyObject]
print("***************************\(resultDict)")
// Get the first item from the returned items
if let itemsArray = (resultDict as AnyObject).value(forKey: element) as? NSArray {
// Remove all existing video data
self.videosArray.removeAll()
for index in 0..<itemsArray.count {
// Append the desiredVaules dictionary to the videos array
self.videosArray.append(self.unwrapYoutubeJson(arrayToBeUnwrapped: itemsArray, index: index))
}
// Asynchronously reload the data and display on the tableview
DispatchQueue.main.async {
// Reload the tableview
self.searchResultTableView.reloadData()
}
}
} catch let jsonError {
print(jsonError)
}
}
})
// Execute the task
task.resume()
}
func unwrapYoutubeJson(arrayToBeUnwrapped: NSArray, index: Int) -> [String: AnyObject]{
let firstItemDict = arrayToBeUnwrapped[index] as! [String: AnyObject]
// Get the snippet dictionary that contains the desired data
let snippetDict = firstItemDict["snippet"] as! [String: AnyObject]
// Dictionary to store desired video contents for display on tableview
// desired values - "Title", "Description", "Thumbnail"
var desiredValuesDict = [String: AnyObject]()
desiredValuesDict["title"] = snippetDict["title"]
desiredValuesDict["description"] = snippetDict["description"]
// Further unwrap to get the Thumbnail default URL
let thumbnailDict: [String: AnyObject]
thumbnailDict = snippetDict["thumbnails"] as! [String: AnyObject]
let defaultThumbnailDict = thumbnailDict["default"] as! [String: AnyObject]
desiredValuesDict["thumbnail"] = defaultThumbnailDict["url"]
//Get the id dictionary that contains videoId
let idDict = firstItemDict["id"] as? [String: AnyObject]
desiredValuesDict["videoId"] = idDict?["videoId"]
return desiredValuesDict
}
// Helper function: Given a dictionary of parameters, convert to a string for a url
func escapedParameters(_ parameters: [String : AnyObject]) -> String {
var urlVars = [String]()
for (key, value) in parameters {
// Make sure that it is a string value
let stringValue = "\(value)"
// Escape it
let escapedValue = stringValue.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
//Append it
urlVars += [key + "=" + "\(escapedValue!)"]
}
return (!urlVars.isEmpty ? "" : "") + urlVars.joined(separator: "&")
}
// MARK: UITableView method implementation
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SearchResultTableViewCell
let videoSelected = videosArray[indexPath.row]
cell.updateIU(video: videoSelected)
cell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
let id = videosArray[indexPath.row]["videoId"] as? String
print("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\(id)")
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videosArray.count
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? playerViewController {
if let selectedRowIndexPath = searchResultTableView.indexPathForSelectedRow?.row {
destination.mytitle = videosArray[selectedRowIndexPath]["title"] as! String
destination.mydescript = videosArray[selectedRowIndexPath]["description"] as! String
destination.myvideoId = videosArray[selectedRowIndexPath] ["videoId"] as? String
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
getVideoList()
searchResultTableView.dataSource = self
searchResultTableView.delegate = self
}
}
Question 2 :
When I am trying to play a video using YTPlayerHelper it's not working:
fatal error: unexpectedly found nil while unwrapping an Optional value and the video ID appears as nil.
How can I play the video using the YTPlayerHelper? This is how I am playing the video:
import UIKit
import youtube_ios_player_helper
class playerViewController: UIViewController {
#IBOutlet weak var MyPlayer: YTPlayerView!
#IBOutlet weak var txtTitle: UITextView!
#IBOutlet weak var txtDescript: UITextView!
var mytitle: String!
var mydescript: String!
var myvideoId : String!
override func viewDidLoad() {
super.viewDidLoad()
print("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\(myvideoId)")
MyPlayer.load(withVideoId: myvideoId!)
txtTitle.text = mytitle
txtDescript.text = mydescript
}
}
Here is my Alamofire implementation; you have to adjust the names to match yours:
func callAlamo(url : String) {
Alamofire.request(url).responseJSON(completionHandler: {
response in
self.parseData(JSONData: response.data!)
})
}
func parseData(JSONData : Data) {
do {
var readableJSON = try JSONSerialization.jsonObject(with: JSONData, options: .mutableContainers) as! JSONStandard
pageToken = readableJSON["nextPageToken"] as! String
if previousPageButton.isEnabled {
previousPageToken = readableJSON["prevPageToken"] as? String
}
if previousPageToken == nil {
previousPageButton.isEnabled = false
}
if let items = readableJSON["items"] as? [JSONStandard] {
for i in 0..<items.count {
let item = items[i]
var name = String()
var previewURL1 = String()
if let id = item["id"] as? JSONStandard {
let url = id["videoId"] as! String
previewURL1 = url
}
let previewURL = previewURL1
if let snippet = item["snippet"] as? JSONStandard {
let title = snippet["title"] as! String
name = title
if let thumbnails = snippet["thumbnails"] as? JSONStandard {
if let images = thumbnails["high"] as? JSONStandard {
let mainImageURL = URL(string: images["url"] as! String)
imageURL = images["url"] as! String
let mainImageData = NSData(contentsOf: mainImageURL!)
let mainImage = UIImage(data: mainImageData! as Data)
posts.append(post.init(mainImage: mainImage, name: name, previewURL: previewURL, imageURL: imageURL))
self.tableView.reloadData()
nextPageButton.isEnabled = true
}
}
}
}
}
} catch {
print(error)
}
}
Then make a request by using callAlamo(url: yourURL), replacing yourURL with the actual URL.
For the second question, you have a great tutorial here: http://www.appcoda.com/youtube-api-ios-tutorial/
In the tutorial is another way to update UITableView with YouTube videos, but personally I prefer the Alamofire one, as it is much faster and easier to write. I recommend to view just the playing videos part.
I'm creating a simple chat app, it has a loading screen with a segue to either the login screen if the user is not logged in or directly to his chats if he is. The chats are displayed in a UICollectionView. When I was first testing, I populated it with dummy data which I declared in the class itself, and everything worked fine. Now I am fetching the user's chats from an online database in the Loading Screen, and storing them in an array called user_chats which is declared globally.
I use the following code to populate the UICollectionView :
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// getUserChats()
return user_chats.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("chat_cell" , forIndexPath: indexPath) as! SingleChat
cell.chatName?.text = user_chats[indexPath.row].chat_partner!.name
cell.chatTextPreview?.text = user_chats[indexPath.row].chat_messages!.last!.text
let profile_pic_URL = NSURL(string : user_chats[indexPath.row].chat_partner!.profile_pic!)
downloadImage(profile_pic_URL!, imageView: cell.chatProfilePic)
cell.chatProfilePic.layer.cornerRadius = 26.5
cell.chatProfilePic.layer.masksToBounds = true
let dividerLineView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(white: 0.5, alpha: 0.5)
return view
}()
dividerLineView.translatesAutoresizingMaskIntoConstraints = false
cell.addSubview(dividerLineView)
cell.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-1-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": dividerLineView]))
cell.addSubview(dividerLineView)
cell.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[v0(1)]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": dividerLineView]))
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("showChat", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "showChat") {
let IndexPaths = self.collectionView!.indexPathsForSelectedItems()!
let IndexPath = IndexPaths[0] as NSIndexPath
let vc = segue.destinationViewController as! SingleChatFull
vc.title = user_chats[IndexPath.row].chat_partner!.name
}
}
DATA FETCH :
func getUserChats() {
let scriptUrl = "*****"
let userID = self.defaults.stringForKey("userId")
let params = "user_id=" + userID!
let myUrl = NSURL(string: scriptUrl);
let request: NSMutableURLRequest = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let data = params.dataUsingEncoding(NSUTF8StringEncoding)
request.timeoutInterval = 10
request.HTTPBody=data
request.HTTPShouldHandleCookies=false
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
let queue:NSOperationQueue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse?, data: NSData?, error: NSError?) -> Void in
do {
if (data != nil) {
do {
var dataString = String(data: data!, encoding: NSUTF8StringEncoding)
var delimiter = "]"
var token = dataString!.componentsSeparatedByString(delimiter)
dataString = token[0] + "]"
print(dataString)
let data_fixed = dataString!.dataUsingEncoding(NSUTF8StringEncoding)
do {
let jsonArray = try NSJSONSerialization.JSONObjectWithData(data_fixed!, options:[])
// LOOP THROUGH JSON ARRAY AND FETCH VALUES
for anItem in jsonArray as! [Dictionary<String, AnyObject>] {
let curr_chat = Chat()
if let chatId = anItem["chatId"] as? String {
curr_chat.id = chatId
}
let friend = Friend()
let user1id = anItem["user1_id"] as! String
let user2id = anItem["user2_id"] as! String
if (user1id == userID) {
if let user2id = anItem["user2_id"] as? String {
friend.id = user2id
}
if let user2name = anItem["user2_name"] as? String {
friend.name = user2name
}
if let user2profilepic = anItem["user2_profile_pic"] as? String {
friend.profile_pic = user2profilepic
}
}
else if (user2id == userID){
if let user1id = anItem["user1_id"] as? String {
friend.id = user1id
}
if let user1name = anItem["user1_name"] as? String {
friend.name = user1name
}
if let user1profilepic = anItem["user1_profile_pic"] as? String {
friend.profile_pic = user1profilepic
}
}
curr_chat.chat_partner = friend
var chat_messages = [Message]()
if let dataArray = anItem["message"] as? [String : AnyObject] {
for (_, messageDictionary) in dataArray {
if let onemessage = messageDictionary as? [String : AnyObject] { let curr_message = Message()
if let messageid = onemessage["message_id"] as? String {
curr_message.id = messageid
}
if let messagedate = onemessage["timestamp"] as? String {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = dateFormatter.dateFromString(messagedate)
curr_message.date = date
}
if let messagesender = onemessage["sender"] as? String {
curr_message.sender = messagesender
}
if let messagetext = onemessage["text"] as? String {
curr_message.text = messagetext
}
chat_messages.append(curr_message)
}}
}
curr_chat.chat_messages = chat_messages
user_chats.append(curr_chat)
}
}
catch {
print("Error: \(error)")
}
}
// NSUserDefaults.standardUserDefaults().setObject(user_chats, forKey: "userChats")
}
else {
dispatch_async(dispatch_get_main_queue(), {
let uiAlert = UIAlertController(title: "No Internet Connection", message: "Please check your internet connection.", preferredStyle: UIAlertControllerStyle.Alert)
uiAlert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { action in
self.dismissViewControllerAnimated(true, completion:nil)
}))
self.presentViewController(uiAlert, animated: true, completion: nil)
})
}
} catch _ {
NSLog("error")
}
})
}
The problem is that the collection view is always empty now. I have done some debugging and put a breakpoint inside the first function, and I saw that this method is called when the Loading Screen is still displayed to the user and the chat screen hasn't even been loaded. My suspicion is that this is called before the data is fetched from the internet in the Loading Screen, and as a result the size of the user_chats array is 0. I am used to working with Android and ListView where the ListView are never populated until the parent view is displayed on screen, hence why I am confused. The method which fetches the data from the online database works fine as I have already extensively debugged it, so the problem isn't there.
The best option is to add a completionHandler to your function to be notified when the data is return and/or when the async function is finished executing. The code below is a truncated version of your getUserCharts function with a completionHandler, which returns a true or false when the data is load (You could modify this to return anything you wish). You can read more about closures/ completion handlers https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html or google.
function
func getUserChats(completionHandler: (loaded: Bool, dataNil: Bool) -> ()) -> (){
NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse?, data: NSData?, error: NSError?) -> Void in
do {
if (data != nil) {
do {
var dataString = String(data: data!, encoding: NSUTF8StringEncoding)
var delimiter = "]"
var token = dataString!.componentsSeparatedByString(delimiter)
dataString = token[0] + "]"
print(dataString)
let data_fixed = dataString!.dataUsingEncoding(NSUTF8StringEncoding)
do {
let jsonArray = try NSJSONSerialization.JSONObjectWithData(data_fixed!, options:[])
// LOOP THROUGH JSON ARRAY AND FETCH VALUES
completionHandler(loaded: true, dataNil: false)
}
catch {
print("Error: \(error)")
}
}
}
else {
//Handle error or whatever you wish
completionHandler(loaded: true, dataNil: true)
}
} catch _ {
NSLog("error")
}
How to use it
override func viewDidLoad() {
getUserChats(){
status in
if status.loaded == true && status.dataNil == false{
self.collectionView?.reloadData()
}
}
}
It sounds like this is an async issue. I'm not sure how your project is setup but you need to call reloadData() on your collection view when the response is returned.
After you have received the data back from the server, and updated the data source for the collection view you need to refresh the collection view (Make sure you are on the main thread, since it is modifying the UI):
dispatch_async(dispatch_get_main_queue()) {
self.collectionView.reloadData()
}
Edit:
Also, I'm not completely sure how you have your project setup, but you could create a delegate for your data fetch, so every time you get something back from the server it calls a delegate method that there are new messages. Your collection view controller would subscribe to that delegate, and every time the that method is called it would reload your collection view.
The Delegate:
protocol ChatsDelegate {
func didUpdateChats(chatsArray: NSArray)
}
In your Data Fetch:
user_chats.append(cur_chat)
self.delegate.didUpdateChats(user_chats)
In your collectionView controller:
class viewController: ChatsDelegate, ... {
...
func didUpdateChats(chatsArray: NSArray) {
dispatch_async(dispatch_get_main_queue()) {
self.collectionView.reloadData()
}
}
I have the following two functions in my first ViewController. They load a UITableView with over 300 rows. I call the loadRemoteData function inside the ViewDidLoad. Everything works fine in the first ViewController.
// MARK: - parseJSON
func parseJSON(data: NSData) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
if let rootDictionary = json as? [NSObject: AnyObject], rootResults = rootDictionary["results"] as? [[NSObject: AnyObject]] {
for childResults in rootResults {
if let firstName = childResults["first_name"] as? String,
let lastName = childResults["last_name"] as? String,
let bioguideId = childResults["bioguide_id"] as? String,
let state = childResults["state"] as? String,
let stateName = childResults["state_name"] as? String,
let title = childResults["title"] as? String,
let party = childResults["party"] as? String {
let eachLegislator = Legislator(firstName: firstName, lastName: lastName, bioguideId: bioguideId, state: state, stateName: stateName, title: title, party: party)
legislators.append(eachLegislator)
}
}
}
} catch {
print(error)
}
}
// MARK: - Remote Data configuration
func loadRemoteData() {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let url = "https://somedomain.com/legislators?order=state_name__asc,last_name__asc&fields=first_name,last_name,bioguide_id"
if let url = NSURL(string: url) {
let task = session.dataTaskWithURL(url, completionHandler: { (data, response, error) -> Void in
if let error = error {
print("Data Task failed with error: \(error)")
return
}
if let http = response as? NSHTTPURLResponse, data = data {
if http.statusCode == 200 {
dispatch_async(dispatch_get_main_queue()) {
self.parseJSON(data)
self.tableView.reloadData()
}
}
}
})
task.resume()
}
}
In the second ViewController, I want to display more information about the individual listed in the cell that is tapped, for that I use a different URL such as https://somedomain.com/legislators?bioguide_id=\"\(bioguideId)\" which provides me with a lot more detail. (The data being requested from the JSON Dictionary is different)
The code I use in the second ViewController is just like shown above with the only difference being the URL. I can print the url coming from the previous ViewController and it is displayed in the console log but no json data is shown.
I would appreciate any help.
Thanks
Below is the code for my second ViewController:
import UIKit
class DetailViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var bioguideId: String?
var currentLegislator: Legislator? = nil
var currentLegislatorUrl: String?
let reuseIdentifier = "Cell"
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var tableView: UITableView!
// MARK: - parseJSON
private func parseJSON(data: NSData) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
if let rootDictionary = json as? [NSObject: AnyObject],
rootResults = rootDictionary["results"] as? [[NSObject: AnyObject]] {
for childResults in rootResults {
if let firstName = childResults["first_name"] as? String,
let lastName = childResults["last_name"] as? String,
let bioguideId = childResults["bioguide_id"] as? String,
let state = childResults["state"] as? String,
let stateName = childResults["state_name"] as? String,
let title = childResults["title"] as? String,
let party = childResults["party"] as? String {
currentLegislator = Legislator(firstName: firstName, lastName: lastName, bioguideId: bioguideId, state: state, stateName: stateName, title: title, party: party)
}
}
}
} catch {
print(error)
}
}
// MARK: - Remote Data configuration
func loadRemoteData(url: String) {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let url = currentLegislatorUrl
if let url = NSURL(string: url!) {
let task = session.dataTaskWithURL(url, completionHandler: { (data, response, error) -> Void in
if let error = error {
print("Data Task failed with error: \(error)")
return
}
print("Success")
if let http = response as? NSHTTPURLResponse, data = data {
if http.statusCode == 200 {
dispatch_async(dispatch_get_main_queue()) {
self.parseJSON(data)
self.tableView.reloadData()
}
}
}
})
task.resume()
}
}
func loadImage(urlString:String) {
let imgURL: NSURL = NSURL(string: urlString)!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if (error == nil && data != nil) {
func display_image() {
self.imageView.image = UIImage(data: data!)
}
dispatch_async(dispatch_get_main_queue(), display_image)
}
}
task.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
print(currentLegislatorUrl!)
loadRemoteData(currentLegislatorUrl!)
loadImage("https://theunitedstates.io/images/congress/225x275/\(bioguideId!).jpg")
self.title = bioguideId
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath)
cell.textLabel!.text = currentLegislator?.firstName
return cell
}
}
Thanks to Adam H. His comment made me reevaluate the URL I was using and by adding additional operators, now the data is shown in my second ViewController.
I have a Problem with Swift and Xcode 7.
class ConnectVC: UITableViewController {
var username:Array< String > = Array < String >()
var TableData:Array< String > = Array < String >()
var pictures:Array< String > = Array < String >()
var profile_pictures:Array< String > = Array < String >()
override func viewDidLoad() {
super.viewDidLoad()
get_data_from_url("-url-")
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return TableData.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! ConnectVCCell
let picture = pictures[indexPath.row]
print(pictures.count)
print(picture)
print(profile_pictures.count)
let pic = profile_pictures[indexPath.row]
if picture != "" {
let aString = "-url-"
let url = NSURL(string: aString)
let data = NSData(contentsOfURL: url!)
print(url)
let image = UIImage(data: data!)
cell.imageURL.image = image
}else{
print("No picture")
cell.imageURL.image = nil
}
cell.mainLabel.text = TableData[indexPath.row]
return cell
}
func get_data_from_url(url:String)
{
let url = NSURL(string: url)
let urlRequest = NSMutableURLRequest(URL: url!,
cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 15.0)
let queue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(
urlRequest,
queue: queue,
completionHandler: {response, data, error in
if data!.length > 0 && error == nil{
let json = NSString(data: data!, encoding: NSASCIIStringEncoding)
self.extract_json(json!)
}else if data!.length == 0 && error == nil{
print("Nothing was downloaded")
} else if error != nil{
print("Error happened = \(error)")
}
}
)
}
func extract_json(data:NSString)
{
let jsonData:NSData = data.dataUsingEncoding(NSASCIIStringEncoding)!
do {
// Try parsing some valid JSON
let json: AnyObject? = try NSJSONSerialization.JSONObjectWithData(jsonData, options: .AllowFragments)
let data_list = json as? NSArray
for (var i = 0; i < data_list!.count ; i++ )
{
if let data_obj = data_list![i] as? NSDictionary
{
if let text = data_obj["text"] as? String
{
if let picture = data_obj["picture"] as? String
{
if let user = data_obj["user"] as? String
{
self.save_image("-url-")
TableData.append(text + " [" + user + "]")
pictures.append(picture)
}
}
}
}
}
}
catch let error as NSError {
// Catch fires here, with an NSErrro being thrown from the JSONObjectWithData method
print("A JSON parsing error occurred, here are the details:\n \(error)")
}
do_table_refresh();
}
func save_image(url:String){
let url = NSURL(string: url)
let urlRequest = NSMutableURLRequest(URL: url!,
cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 15.0)
let queue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(
urlRequest,
queue: queue,
completionHandler: {response, data, error in
if data!.length > 0 && error == nil{
let json = NSString(data: data!, encoding: NSASCIIStringEncoding)
self.extract_json_picture(json!)
}else if data!.length == 0 && error == nil{
print("Nothing was downloaded")
} else if error != nil{
print("Error happened = \(error)")
}
}
)
}
func extract_json_picture(data:NSString)
{
let jsonData:NSData = data.dataUsingEncoding(NSASCIIStringEncoding)!
do {
// Try parsing some valid JSON
let json: AnyObject? = try NSJSONSerialization.JSONObjectWithData(jsonData, options: .AllowFragments)
print(json)
let user_info = json as? NSArray
if let user_list = user_info![0] as? NSDictionary
{
if let profile_picture = user_list["picture"] as? String
{
profile_pictures.append(profile_picture)
}
}
}
catch{
print("A JSON parsing error occurred, here are the details:\n \(error)")
}
With this Code I get the following Error:
fatal error: Array index out of range
in the following line:
let pic = profile_pictures[indexPath.row]
The Problem is, that the array is empty. But I don't see the Problem. I think the function where I fill the array is correctly called.
Can someone help?
`tableViewcellForRowAtIndexPath: is getting called before TableData has data.
Ensure your number of rows in section returns the number of pictures:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return profile_pictures.count
}
This prevents a cell from being created when a picture doesn't exist at that index.