Passing data from a class to same instance of a viewcontroller - ios

When a specific event happens(in my case when a tab bar is changed) I want to create a new link from an Array. I have gotten this to work but the problem I am not facing is when i try to pass the generated link to the same viewcontroller i get an error
fatal error: unexpectedly found nil while unwrapping an Optional value
This happens when I try to change the UILabel movietitle and imageview. I think this is because every time it sends the link it creates a new ViewController instead of using the existing one. Might also be that i have missed an unwrapped value somewhere. Hope someone here can help me!
StringBuilder:
import UIKit
class StringBuilder: NSObject {
let urlString = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acb9e5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=12"
let urlStringMultipleGenres = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acbf5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=28,12,10749"
var currentGenreArray: Array<Int> = []
//This will be run after the user has selected or deselected genres in the genreControllerView
func updateGenres(genreArrayIn: Array<Int>){
print("update genres input: ")
print(genreArrayIn)
//If new array input is the same as old arrayinput, do nothing
if genreArrayIn == currentGenreArray{
return
}
else{
let returnedLink = generateString(genreID: genreArrayIn)
print("Returned link after generate string" + returnedLink)
sendLink(link: returnedLink)
}
}
//After the updated genres have been put into an Array, this function will generate the whole string which
//will be the main String the getMovieRequest follows
func generateString(genreID: Array<Int>) -> String{
let filteredGenreArray = filterZeroes(unfilteredArray: genreID)
currentGenreArray = genreID
print("current genre array: ")
print(currentGenreArray)
let baseString = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acbfed4ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres="
var generatedString = baseString
for id in filteredGenreArray{
let k = String(id)
generatedString += k + ","
}
print("Generated Link from Strinbuilder: ")
print(generatedString)
return generatedString
}
func filterZeroes(unfilteredArray: Array<Int>) -> Array<Int>{
let filteredGenreArray = unfilteredArray.filter {$0 > 0}
print("filtered array: ")
print(filteredGenreArray)
return filteredGenreArray
}
func sendLink(link: String){
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let movieVC = storyBoard.instantiateViewController(withIdentifier: "movieView") as! ViewController
movieVC.getMovieData(activeGenreLink: link)
print("new link sent from sendlink()")
}
}
ViewController:
import UIKit
import Alamofire
import AlamofireImage
class ViewController: UIViewController{
static let sharedInstance = ViewController()
var movieIndex = 0
var movieArray:[Movie] = []
var downloadGrp = DispatchGroup()
#IBOutlet var uiMovieTitle: UILabel!
#IBOutlet var uiMoviePoster: UIImageView!
#IBOutlet var posterLoading: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let firstTimeLink = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acb9e5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=35,18"
getMovieData(activeGenreLink: firstTimeLink)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped(tapGestureRecognizer:)))
uiMoviePoster.isUserInteractionEnabled = true
uiMoviePoster.addGestureRecognizer(tapGestureRecognizer)
print("settings Sucessful")
}
func imageTapped(tapGestureRecognizer: UITapGestureRecognizer){
performSegue(withIdentifier: "detailsSegue", sender: self)
}
#IBAction func yesBtn(_ sender: UIButton) {
movieIndex += 1
updateUI()
}
#IBAction func seenBtn(_ sender: UIButton) {
movieIndex += 1
}
#IBAction func noBtn(_ sender: UIButton) {
movieIndex += 1
}
//Get movie data
func getMovieData(activeGenreLink: String){
//self.posterLoading.startAnimating()
movieIndex = 0
self.downloadGrp.enter()
Alamofire.request(activeGenreLink).responseJSON { response in
//print(response.request) // original URL request
//print(response.response) // HTTP URL response
//print(response.data) // server data
//print(response.result) // result of response serialization
self.movieArray = []
print(self.movieArray)
if let json = response.result.value as? Dictionary<String,AnyObject> {
if let movies = json["results"] as? [AnyObject]{
for movie in movies{
let movieObject: Movie = Movie()
let title = movie["title"] as! String
let releaseDate = movie["release_date"] as! String
let posterPath = movie["poster_path"] as! String
let overView = movie["overview"] as! String
let movieId = movie["id"] as! Int
let genre_ids = movie["genre_ids"] as! [AnyObject]
movieObject.title = title
movieObject.movieRelease = releaseDate
movieObject.posterPath = posterPath
movieObject.overView = overView
movieObject.movieId = movieId
for genre in genre_ids{//Genre ids, fix this
movieObject.movieGenre.append(genre as! Int)
}
Alamofire.request("http://image.tmdb.org/t/p/w1920" + posterPath).responseImage {
response in
//print(response.request)
//print(response.response)
//debugPrint(response.result)
if var image = response.result.value {
image = UIImage(data: response.data!)!
movieObject.poster = image
}
}
self.movieArray.append(movieObject)
}//End of for each movie
}
else{
print("error while making results anyobject")
}
}
else{
print("error while trying to make NSDictionary")}
self.downloadGrp.leave()
}//End of Json request
downloadGrp.notify( queue: .main){
print("all downloads finished")
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
print(self.movieArray[0].title!)
self.updateUI()
print("updatedUI")
}
}
}//End of getmoviedata
override func prepare(for segue: UIStoryboardSegue,sender: Any?){
// Create a variable that you want to send
let currentMovie = self.movieArray[movieIndex]
if let destinationVC = segue.destination as? DetailsViewController{
destinationVC.currentMovie = currentMovie
}
}
func updateUI(){
//self.posterLoading.stopAnimating()
if uiMoviePoster == nil{
print(uiMovieTitle.debugDescription)
}
else{
print("first time debugID: " + uiMovieTitle.debugDescription)
uiMovieTitle.text = self.movieArray[movieIndex].title
uiMoviePoster.image = self.movieArray[movieIndex].poster
}
}
}

You want to grab a sharedInstance not instantiate from a storyboard
let movieVC = ViewController.sharedInstance()
but I still do not understand why do you need to do it like this

Related

How can i Solve "Thread 1: Fatal error: Index out of range" error

Hey i got "Thread 1: Fatal error: Index out of range" (in this line selectedId = idArray[indexPath.row] ) error every time i clicked any cell at table view. How i can solve this problem. I think i have problem with my arrays but i cant figure that out. I was do same thing at my last app but i couldnt get any error.
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var nameArray = [String]()
var idArray = [UUID?]()
var selectedName = ""
var selectedId : UUID?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
navigationController?.navigationBar.topItem?.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.add, target: self, action: #selector(addNewPatient))
getData()
}
override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(getData) , name: NSNotification.Name(rawValue: "newData"), object: nil)
}
#objc func getData() {
nameArray.removeAll(keepingCapacity: false)
idArray.removeAll(keepingCapacity: false)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ToName")
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try context.fetch(fetchRequest)
if results.count > 0 {
for result in results as! [NSManagedObject] {
if let name = result.value(forKey: "name") as? String {
self.nameArray.append(name)
}
if let id = result.value(forKey: "id") as? UUID {
self.idArray.append(id)
}
}
}
} catch {
print("error")
}
tableView.reloadData()
}
#objc func addNewPatient() {
selectedName = ""
performSegue(withIdentifier: "toNameVC", sender: nil)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
var content = cell.defaultContentConfiguration()
content.text = nameArray[indexPath.row]
// content.secondaryText = "secondary test"
cell.contentConfiguration = content
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toNameVC" {
let destinationVC = segue.destination as! ToNameViewController
destinationVC.choosenName = selectedName
destinationVC.choosenId = selectedId
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedName = nameArray[indexPath.row]
selectedId = idArray[indexPath.row]
performSegue(withIdentifier: "toNameVC", sender: nil)
}
}
This is my toNameViewController.swift page
ToNameViewController.swift
Patient Record App
import UIKit
import CoreData
class ToNameViewController: UIViewController {
#IBOutlet weak var nameLabelText: UITextField!
#IBOutlet weak var tcLabelText: UITextField!
#IBOutlet weak var birthDateLabelText: UITextField!
var choosenName = ""
var choosenId : UUID?
override func viewDidLoad() {
super.viewDidLoad()
if choosenName != "" {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ToName")
let idString = choosenId?.uuidString
fetchRequest.predicate = NSPredicate(format: "id = %#", idString!)
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try context.fetch(fetchRequest)
if results.count > 0 {
for result in results as! [NSManagedObject] {
if let name = result.value(forKey: "name") as? String {
nameLabelText.text = name
}
if let tc = result.value(forKey: "tc") as? Int {
tcLabelText.text = String(tc)
}
if let birth = result.value(forKey: "birth") as? Int {
birthDateLabelText.text = String(birth)
}
}
}
} catch {
print("error")
}
}else {
}
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(hiddenKeyboard))
view?.addGestureRecognizer(gestureRecognizer)
}
#objc func hiddenKeyboard() {
view?.endEditing(true)
}
#IBAction func saveButton(_ sender: Any) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let newPatient = NSEntityDescription.insertNewObject(forEntityName: "ToName", into: context)
newPatient.setValue(nameLabelText.text, forKey: "name")
if let tc = Int(tcLabelText.text!) {
newPatient.setValue(tc, forKey: "tc")
}
if let birth = Int(birthDateLabelText.text!) {
newPatient.setValue(birth, forKey: "birth")
}
do {
try context.save()
print("saved")
} catch {
print("error")
}
NotificationCenter.default.post(name: NSNotification.Name("newData"), object: nil)
self.navigationController?.popViewController(animated: true)
}
}
This is a common mistake: Multiple arrays for the data source is extremely error-prone if both arrays are populated with optionals.
Declare a custom struct
struct Item {
let name: String
let id: UUID?
}
Declare the data source
var items = [Item]()
Populate the array (valueForKey syntax is outdated)
let fetchRequest = NSFetchRequest<ToName>(entityName: "ToName")
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try context.fetch(fetchRequest)
for result in results {
if let name = result.name {
self.items.append(Item(name: name, id: result.id))
}
}
tableView.reloadData()
} catch {
print(error)
}
In numberOfRowsInSection
return items.count
In cellForRowAt
content.text = items[indexPath.row].name
And in didSelectRowAt
let item = items[indexPath.row]
selectedName = item.name
selectedId = item.id
You can even pass the Item instance to the second view controller rather than the two selected... properties.
But why not even
var toNames = [ToName]()
This avoids any out of range crash
Inside your getData function u append values to idArray. But result.value(forKey: "id") is optional and values get appended unless it is nil. So there may be a difference between the count between nameArray and idArray. So if the result.value(forKey: "id") is nil append a default value to idArray.
#objc func getData() {
nameArray.removeAll(keepingCapacity: false)
idArray.removeAll(keepingCapacity: false)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ToName")
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try context.fetch(fetchRequest)
if results.count > 0 {
for result in results as! [NSManagedObject] {
if let name = result.value(forKey: "name") as? String {
self.nameArray.append(name)
//change here like this
if let id = result.value(forKey: "id") as? UUID {
self.idArray.append(id)
}else{
self.idArray.append("your_default_value")//default value or nil
}
}
}
} catch {
print("error")
}
tableView.reloadData()
}
Add an extension to the Array:
extension Array {
func object(at index: Int) -> Element? {
if index < count {
return self[index]
} else {
return nil
}
}
}
Now use this method whenever you access the element from the array.

Table View is empty when I switch Tab Bar controller screens

I have a Tab bar Controller to manage all the views. The problem I'm having is when I call a function in searchViewController (doAThing()) from HomeViewController which reloads the tableView in searchViewController, the tableView is empty when the Tab bar controller switches views.
Why does calling the doAThing method in my searchViewController not refresh my tableView?
How can I fill my tableView with values.
HomeViewController.swift
import UIKit
class HomeViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UISearchBarDelegate{
#IBOutlet weak var productCollectionView: UICollectionView!
#IBOutlet weak var storesCollectionView: UICollectionView!
#IBOutlet var searchQ: UISearchBar!
#IBOutlet weak var scrollView: UIScrollView!
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if(collectionView == storesCollectionView) {
return storesImages.count
}
return productsImages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if(collectionView == storesCollectionView) {
let cell2 = storesCollectionView.dequeueReusableCell(withReuseIdentifier: "storesCell", for: indexPath) as! StoreCollectionViewCell
cell2.compstoreImage.image = UIImage(named: storesImages[indexPath.row])
return cell2
}
else{
let cell = productCollectionView.dequeueReusableCell(withReuseIdentifier: "productsCell", for: indexPath) as! ProductCollectionViewCell
cell.pillImage.image = UIImage(named: productsImages[indexPath.row])
return cell
}
}
var productsImages:[String] = ["pcPic", "picturePC"]
var storesImages:[String] = ["newarkStore", "compeStore"]
override func viewDidLoad() {
super.viewDidLoad()
searchQ.delegate = self
scrollView.contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height)
// searchQ.delegate = self
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//self.tabBarController?.tabBar.isHidden = false
}
func searchBarSearchButtonClicked(_ searchQ: UISearchBar)
{
print("*********")
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let resultViewController = storyBoard.instantiateViewController(withIdentifier: "SearchViewController") as! searchViewController
// resultViewController.searchQuery = searchQ
resultViewController.doAthing(searchQ)
self.tabBarController?.selectedIndex = 3
// NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
resultViewController.resultsView.reloadData()
}
// func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
//
// print("*********")
//
// let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "SearchViewController") as! searchViewController
// secondViewController.doAthing(searchBar)
// self.navigationController!.pushViewController(secondViewController, animated: true)
//
//
//
// //let titles: Elements = try doc.select("a[product-thumb]")
// //let titles: String = try doc.select("a").attr("product-thumb")
//
// // print(titles
//
// hideKeyboardWhenTappedAround()
//
// }
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
searchViewController.swift
import UIKit
import SwiftSoup
class searchViewController: UIViewController{
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var resultsView: UITableView!
#IBOutlet var searchQuery: UISearchBar!
// let content = try! String(contentsOf: URL(string: "https://www.locally.com/search/all/activities/depts?q=bottle")!)
// let doc: Document = try! SwiftSoup.parse(content)
var products: [String] = []
//let products = ["Computer", "PC", "Laptop"]
let stores = ["Computer Central", "Fry's Electronics", "Best Buy"]
//var stores: [String] = []
let into = ["Custom PC with high performanc. Perfect for gaming and streaming. Great condition", "Custom PC with high performanc. Perfect for gaming and streaming. Great condition", "Custom PC with high performanc. Perfect for gaming and streaming. Great condition"]
var dollars: [String] = []
//let dollars = [100, 220, 129, 100, 220, 129]
let likes = [10, 24, 24, 24 , 456, 46, 46]
//let miles = ["3.2 mi", "4.1 mi", "6.3 mi", "3.2 mi", "4.1 mi", "6.3 mi"]
var miles: [String] = []
// let names = ["Central Computers", "CompE", "geekStore", "Central Computers", "CompE", "geekStore"]
var names: [String] = []
let numbers = ["510-329-0172", "510-456-7345", "510-329-0172", "510-329-0172", "510-456-7345", "510-329-0172"]
var images: [UIImage] = []
var categories: [String] = []
var descriptions: [String] = []
var stars: [String] = []
var ratings: [Double] = []
var ratingImage: [[UIImage]] = [[]]
var phones: [String] = []
var cities: [String] = []
//var webCounter:Int = 0
var x:Double = 0
var y:Int = 0
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
searchQuery.delegate = self
resultsView.delegate = self
resultsView.dataSource = self
//set the height of each row in tableview
self.resultsView.rowHeight = 200.0
// Do any additional setup after loading the view.
}
#objc func loadList(notification: NSNotification) {
//load data here
self.resultsView.reloadData()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
//searchBar.inputViewController?.dismissKeyboard()
searchBar.inputViewController?.dismiss(animated: true)
doAthing(searchBar)
self.searchQuery.endEditing(true)
self.resultsView.keyboardDismissMode = .onDrag
hideKeyboardWhenTappedAround()
}
func doAthing(_ searchBar: UISearchBar) {
do {
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.style = UIActivityIndicatorView.Style.medium
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)
print("reached here")
let html = try String(contentsOf: URL(string: "https://www.locally.com/search/all/activities/depts?q=" + searchBar.text!)!)
//let doc: Document = try SwiftSoup.parse(html)
guard let titles: Elements = try? SwiftSoup.parse(html).getElementsByClass("product-thumb ") else {return}//select("a") else {return}
guard let prices: Elements = try? SwiftSoup.parse(html).getElementsByClass("product-thumb-price dl-price") else {return}
guard let Stores: Elements = try? SwiftSoup.parse(html).getElementsByClass("filter-label-link") else {return}
guard let Images: Elements = try? SwiftSoup.parse(html).getElementsByClass("product-thumb-img") else {return}
products = []
miles = []
dollars = []
names = []
images = []
stars = []
for title: Element in titles.array() {
print("title" + String(titles.size()))
products.append(try! title.attr("data-product-name"))
guard let url: URL = try? URL(string: "https://www.locally.com/" + String(try! title.attr("href")))
else
{
miles.append("-")
continue
}
let html1 = try String(contentsOf: url)
guard let distances: Element = try? SwiftSoup.parse(html1).getElementsByClass("conv-section-distance dl-store-distance").first()
else
{
miles.append("-")
continue
}
print(try! distances.ownText())
miles.append(try! distances.ownText())
guard let K: Array<String> = try? SwiftSoup.parse(html1).getElementsByClass("breadcrumbs container").eachText() else {return}
let str = (try! K.last!.components(separatedBy: "/") )
categories.append(str[str.count - 2])
print(str[str.count - 2])
guard let desc: Array<String> = try? SwiftSoup.parse(html1).getElementsByClass("pdp-information").eachText() else {return}
guard let s:String = try? desc[1]
else {
return
}
descriptions.append( String( s.suffix(s.count - 19) ) )
print( String( s.suffix(s.count - 19) ) )
guard let star: Element = try? SwiftSoup.parse(html1).getElementsByClass("stars").first()
else
{
print("no reviews")
ratings.append(0)
continue
}
print(try! star.attr("data-rating"))
//ratings.append(try! star.attr("data-rating")) ?? ()
let x = Double(try! star.attr("data-rating")) ?? 0
print("y: " + String(Int(x)))
ratings.append(x)
guard let locations: Element = try? SwiftSoup.parse(html1).getElementsByClass("conv-section-store-address section-subtitle dl-store-address js-store-location").first()
else
{
print("cant find city")
return
}
let string = locations.ownText()
print("location: " + String(string.prefix(string.count - 10)) )
cities.append(try! String(string.prefix(string.count - 10)))
guard let phoneNums: Element = try? SwiftSoup.parse(html1).getElementsByClass("selected-retailer-info-link btn-action-sm tooltip").first()
else
{
print("link not found")
return
}
guard let urls:URL = try? URL(string: "https://www.locally.com/" + phoneNums.attr("href") )
else
{
return
}
let html2 = try String(contentsOf: urls )
guard let storePage:Element = try? SwiftSoup.parse(html2).getElementsByClass("landing-page-phone-label").first() else {
print("Phone Number not found")
return
}
let sp = try? storePage.ownText
if let s = sp {
phones.append(try! s())
}
else {
phones.append("N/A")
}
print(try! storePage.ownText())
//
// let html1 = try String(contentsOf: url)
}
for price: Element in prices.array() {
print("prices" + String(prices.size()))
print(String(try! price.ownText()))
dollars.append(try! price.ownText())
}
for store: Element in Stores.array() {
print("Stores" + String(Stores.size()))
names.append(try! store.ownText()) ?? names.append("N/A")
}
for image: Element in Images.array() {
guard let url = URL(string: try! image.attr("src") ) else { return }
let data = try? Data(contentsOf: url)
if let imageData = data {
images.append( UIImage(data: imageData)! )
}
else {
images.append(UIImage(named: "pcPic")!)
}
//images.append(try! image.downloaded(from: image.attr("src")))
}
dismiss(animated: false, completion: nil)
resultsView.reloadData()
//let titles: Elements = try doc.select("a[product-thumb]")
//let titles: String = try doc.select("a").attr("product-thumb")
// print(titles)
} catch Exception.Error(type: let type, Message: let message) {
print(type)
print(message)
} catch {
print("")
}
}
// override func viewWillAppear(_ animated: Bool) {
// super.viewWillAppear(animated)
// self.tabBarController?.tabBar.isHidden = false
// }
//When user changes segment, tableview is reloaded
#IBAction func segmentChanged(_ sender: Any) {
resultsView.reloadData();
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
func imageWithImage(image: UIImage, scaledToSize newSize: CGSize) -> UIImage {
UIGraphicsBeginImageContext(newSize)
image.draw(in: CGRect(x: 0 ,y: 20 ,width: newSize.width ,height: newSize.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!.withRenderingMode(.alwaysOriginal)
}
}
extension searchViewController: UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("reached here")
switch segmentedControl.selectedSegmentIndex {
case 0:
return products.count
case 1:
return stores.count
case 2:
return (products.count + stores.count)
default:
break
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "resultsTableViewCell") as! resultsTableViewCell
switch segmentedControl.selectedSegmentIndex {
case 0:
cell.titleLabel?.text = products[indexPath.row]
cell.productImage?.image = images[indexPath.row]
// cell.descriptionLabel?.text = into[indexPath.row]
// cell.descriptionLabel?.numberOfLines = 0
cell.price?.text = String(dollars[indexPath.row])
cell.distance?.text = miles[indexPath.row]
cell.storeName?.numberOfLines = 0
cell.storeName?.text = names[indexPath.row]
cell.phoneNumber?.text = phones[indexPath.row]
let ratNumber = ratings[indexPath.row]
if( ratNumber == 0 )
{
cell.rating?.text = "No Reviews"
}
else
{
cell.rating?.text = String(ratNumber)
}
if(ratNumber > 4.5)
{
cell.star1.image = UIImage(named: "regular_5")
}
else if(ratNumber > 4.0)
{
cell.star1.image = UIImage(named: "regular_4_half")
}
else if(ratNumber == 4.0)
{
cell.star1.image = UIImage(named: "regular_4")
}
else if(ratNumber > 3.0)
{
cell.star1.image = UIImage(named: "regular_3_half")
}
else if(ratNumber == 3.0)
{
cell.star1.image = UIImage(named: "regular_3")
}
else if(ratNumber > 2.0)
{
cell.star1.image = UIImage(named: "regular_2_half")
}
else if(ratNumber == 2.0)
{
cell.star1.image = UIImage(named: "regular_2")
}
else if(ratNumber > 1.0)
{
cell.star1.image = UIImage(named: "regular_1_half")
}
else if(ratNumber == 1.0)
{
cell.star1.image = UIImage(named: "regular_1")
}
else
{
cell.star1.image = UIImage(named: "regular_0")
}
cell.rating?.numberOfLines = 0
// cell.phoneNumber?.text = numbers[indexPath.row]
case 1:
cell.titleLabel?.text = stores[indexPath.row]
cell.productImage?.image = imageWithImage(image: UIImage.init(named: "pcPic")!, scaledToSize: CGSize(width: 400, height: 300))
// cell.descriptionLabel?.text = into[indexPath.row]
// cell.descriptionLabel?.numberOfLines = 0
cell.price?.text = String(dollars[indexPath.row])
cell.distance?.text = miles[indexPath.row]
cell.storeName?.text = names[indexPath.row]
cell.phoneNumber?.text = numbers[indexPath.row]
case 2:
var all = products + stores
all.shuffle()
let alls = into + into
cell.titleLabel?.text = all[indexPath.row]
cell.productImage?.image = imageWithImage(image: UIImage.init(named: "pcPic")!, scaledToSize: CGSize(width: 400, height: 300))
// cell.descriptionLabel?.text = alls[indexPath.row]
// cell.descriptionLabel?.numberOfLines = 0
cell.price?.text = String(dollars[indexPath.row])
cell.distance?.text = miles[indexPath.row]
cell.storeName?.text = names[indexPath.row]
cell.phoneNumber?.text = numbers[indexPath.row]
default:
break
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// let vc = self.storyboard?.instantiateViewController(withIdentifier: "ShopViewController") as! ShopViewController
// self.present(vc, animated: true, completion: nil)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationVC = storyboard.instantiateViewController(withIdentifier: "ProductViewController") as! ProductViewController
destinationVC.ktitle = products[indexPath.row]
destinationVC.kprice = dollars[indexPath.row]
// destinationVC.kdescription = [indexPath.row]
destinationVC.kimage = images[indexPath.row]
// destinationVC.klikes = likes[indexPath.row]
destinationVC.kcategory = categories[indexPath.row]
destinationVC.kdescription = descriptions[indexPath.row]
destinationVC.kname = names[indexPath.row]
destinationVC.kmiles = miles[indexPath.row]
destinationVC.klocation = cities[indexPath.row]
self.present(destinationVC, animated: true, completion: nil)
}
}
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard(_:)))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard(_ sender: UITapGestureRecognizer) {
view.endEditing(true)
if let nav = self.navigationController {
nav.view.endEditing(true)
}
}
}
First, it's probably not a good idea to switch to a different tab in that manner. Users understand that tapping a tab-bar icon/button takes them to a new tab. If you're jumping around in the tabs via code, it can be very confusing for the user.
But... it's your app.
The reason your code does not "refresh my tableView" is because you never actually reference that view controller.
In your code:
func searchBarSearchButtonClicked(_ searchQ: UISearchBar)
{
print("*********")
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
// here you create a NEW instance of searchViewController
let resultViewController = storyBoard.instantiateViewController(withIdentifier: "SearchViewController") as! searchViewController
// you call a func in that instance
resultViewController.doAthing(searchQ)
// you switch to tab index 3, which holds an OLD instance of searchViewController
self.tabBarController?.selectedIndex = 3
// you tell resultsView in the NEW instance to reloadData()
resultViewController.resultsView.reloadData()
// as soon as you exit this func, the NEW instance is deleted
}
You could do something like this:
func searchBarSearchButtonClicked(_ searchQ: UISearchBar)
print("*********")
// get a reference to the searchViewController that is already
// part of the tab bar controller
if let tb = self.tabBarController,
let controllers = tb.viewControllers,
let resultVC = controllers[3] as? searchViewController {
// call the func
resultVC.doAthing("new")
tb.selectedIndex = 3
}
}
However, that's really not a great approach... Just for one example, Suppose you change the order of the tabs at some point?
You're better off using either a custom tab bar controller class, or using a "data manager" class.
I'd suggest you head over to google (or your favorite search engine) and search for swift pass data between tab bar view controllers -- you'll find plenty of discussions, articles, examples, etc.
Edit -- comment
In HomeViewController replace your func searchBarSearchButtonClicked with this:
func searchBarSearchButtonClicked(_ searchQ: UISearchBar)
{
print("1 - searchBarSearchButtonClicked")
guard let btnTitle = searchQ.largeContentTitle else {
print("2 - FAILED to get searchQ.largeContentTitle")
return
}
print("2 - got btnTitle", btnTitle)
if let tb = self.tabBarController {
print("3 - got a tab bar controller reference")
if let controllers = tb.viewControllers {
print("4 - got controllers reference")
if controllers.count == 4 {
print("5 - we have 4 controllers")
if let resultVC = controllers[3] as? searchViewController {
print("6 - got a reference to searchViewController")
// if we have not yet selected the 4th tab,
// the view will not yet have been loaded
// so make sure it is
resultVC.loadViewIfNeeded()
// call the func, passing the button title
resultVC.doAthing(searchQ)
// switch to the 4th tab
tb.selectedIndex = 3
print("7 - we should now be at searchViewController")
} else {
print("6 - FAILED TO GET a reference to searchViewController")
}
} else {
print("5 - FAILED TO GET a reference to searchViewController")
}
} else {
print("4 - FAILED TO GET a reference to searchViewController")
}
} else {
print("3 - FAILED TO GET a reference to searchViewController")
}
}
Then see what messages get printed to the debug console.

tableView in XCode is returning cells with random data from array

I am making a simple messaging app in xcode. The table view is populated with custom cells that contain a right and left view. When the user receives a message the left view is populated and when the user sends a message the right view is populated.
The PROBLEM is after reloading the tableView sometimes I will see cells filled with scrambled data from the array containing the messages. I'm not sure what is happening.
Things I've tried:
I've checked the message files that are stored on my backend in Back4App and they all contain the corrent csv data
I looked at my cell count which is always accurate
I've printed the array and cell data before the table reloads and after it reloads and the data in the array's is all correct.
If I leave the message VC and return to it all the messages are displayed correctly but when I stay in the VC and use the send button to send messages the data gets scrambled a little and prints weird combinations of my tableView
You can see the bottom line is not displayed correctly:
Image showing the error
Here is my chat VC:
//
// ChatViewController.swift
// Glam
//
// Created by Student on 11/14/20.
// Copyright © 2020 Tucker Weibell. All rights reserved.
//
import UIKit
import Parse
class ChatViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var navTitle: UINavigationItem!
var senders = [String]()
var message = [String]()
var state = [String]()
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var myView: UIView!
#IBOutlet weak var bottomConstraint: NSLayoutConstraint!
var dataStringSender = ""
var dataStringReciever = ""
var senderName = ""
override func viewDidLoad() {
super.viewDidLoad()
getData()
navTitle.title = MessageCustomerViewController.GlobalVars.selectedItem
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: UIResponder.keyboardWillShowNotification, object: nil)
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
self.view.addGestureRecognizer(tap)
}
func getData() {
let user = PFUser.current()
let username = user?.username
senderName = username!
let query = PFUser.query()
query?.whereKey("username", equalTo: username!)
do {
let result = try query?.findObjects()
let file = result![0]["Messages"] as! PFFileObject
do {
let data = try file.getData()
let string = String(decoding: data, as: UTF8.self)
dataStringSender = string
let cvsRows = string.components(separatedBy: "\n")
for row in cvsRows {
var itemsRow = row.components(separatedBy: ",")
print(itemsRow)
itemsRow[2] = itemsRow[2].replacingOccurrences(of: "\r", with: "")
print(itemsRow)
if itemsRow[0] == MessageCustomerViewController.GlobalVars.selectedItem {
senders.append(itemsRow[0])
message.append(itemsRow[1])
state.append(itemsRow[2])
}
}
}
catch {
print(error.localizedDescription)
}
}
catch {
print(error.localizedDescription)
}
loadData()
}
#IBAction func sendMessage(_ sender: Any) {
let query = PFUser.query()
query?.whereKey("username", equalTo: MessageCustomerViewController.GlobalVars.selectedItem)
do {
let result = try query?.findObjects()
if result?[0]["Messages"] != nil {
let file = result![0]["Messages"] as! PFFileObject
do {
let data = try file.getData()
let string = String(decoding: data, as: UTF8.self)
dataStringReciever = string
}
catch {
print(error)
}
}
}
catch {
print(error)
}
dataStringSender = dataStringSender + "\n" + MessageCustomerViewController.GlobalVars.selectedItem + "," + textView.text + "," + "Sent"
dataStringReciever = dataStringReciever + "\n" + senderName + "," + textView.text + "," + "Recieved"
let dataSent = Data(dataStringSender.utf8)
let dataRecieved = Data(dataStringReciever.utf8)
//let fileSent: PFFileObject = PFFileObject(data: dataSent)!
//let fileRecieved: PFFileObject = PFFileObject(data: dataRecieved)!
let fileSent = PFFileObject(name: "message.csv", data: dataSent)
let fileRecieved = PFFileObject(name: "message.csv", data: dataRecieved)
let user = PFUser.current()
user!["Messages"] = fileSent
user?.saveInBackground()
let newQuery = PFUser.query()
newQuery?.whereKey("username", equalTo: MessageCustomerViewController.GlobalVars.selectedItem)
do {
let newResults = try newQuery?.findObjects()
newResults![0]["Messages"] = fileRecieved
newResults![0].saveInBackground()
}
catch {
print(error)
}
clearData()
getData()
print("\n")
print("\n")
print(message)
print("\n")
print("\n")
print(state)
loadData()
}
func loadData() {
self.tableView.reloadData()
}
func clearData() {
message.removeAll()
state.removeAll()
senders.removeAll()
}
#objc func handleKeyboardNotification(notification: NSNotification) {
if let keyboardFrame: NSValue = notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
var height = keyboardRectangle.height
height = height * -1
bottomConstraint.constant = height + 85
}
}
#objc func dismissKeyboard(sender: UITapGestureRecognizer) {
bottomConstraint.constant = 0
textView.resignFirstResponder()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return message.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print([message[indexPath.row]])
let cell = tableView.dequeueReusableCell(withIdentifier: "messagescell") as! MessagesCell
if state[indexPath.row] == "Sent" {
cell.sentText.text = message[indexPath.row]
cell.sentView.backgroundColor = .clear
}
else {
cell.recievedText.text = message[indexPath.row]
cell.recievedView.backgroundColor = .clear
}
return cell
}
}
That sounds a lot like an reusable cell issue.
Since you are reusing your cells here let cell = tableView.dequeueReusableCell(withIdentifier: "messagescell") as! MessagesCell the properties of those cells happen to have old states from time to time if you not explicitly set new values to all of them.
You should override prepareForReuse() in your custom cell implementation and reset your cell to default values.

Swift Firebase "Cannot assign value of type 'Information' to type 'NSDictionary?'"

I have a tableview that is being populated with who a user is following. Problem is that I need to pass that cells data to "var otherUser: NSDictionary!" but because I am populating the cell using a data structure file called "Information" I get this error - "Cannot assign value of type 'Information' to type 'NSDictionary?'" in the prepareForSegue. I am unsure if I can repackage the information I need into a NSDictionary so I can successfully do a data pass. I just don't know if this is a easy solution or an actual problem because of my ignorance.
Following TableViewController Code
import UIKit
import Firebase
class BusinessFollowing: UITableViewController {
#IBOutlet var noDataView: UIView!
#IBOutlet var followingTableView: UITableView!
var yourFollowing = [Information]()
var listFollowing = [NSDictionary?]()
var databaseRef = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
var loggedInUser = Auth.auth().currentUser
var loggedInUserData:NSDictionary?
var following = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.followingTableView.backgroundView = nil
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.followingTableView.reloadData()
self.yourFollowing.removeAll()
self.following.removeAll()
getFollowingData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "following" {
// gotta check if we're currently searching
if let indexPath = followingTableView.indexPathForSelectedRow {
let user = self.yourFollowing[indexPath.row]
let controller = segue.destination as? ExploreBusinessProfileSwitchView
controller?.otherUser = user
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.yourFollowing.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! BusinessFollowingCell
let following = yourFollowing[indexPath.row]
let businessName = following.businessName
let businessStreet = following.businessStreet
let businessCity = following.businessCity
let businessState = following.businessState
cell.businessName.text = businessName
cell.businessStreet.text = businessStreet
cell.businessCity.text = businessCity
cell.businessState.text = businessState
// cell.businessName?.text = self.listFollowing[indexPath.row]?["businessName"] as? String
// cell.businessStreet?.text = self.listFollowing[indexPath.row]?["businessStreet"] as? String
// cell.businessCity?.text = self.listFollowing[indexPath.row]?["businessCity"] as? String
// cell.businessState?.text = self.listFollowing[indexPath.row]?["businessState"] as? String
return cell
}
func getFollowingData() {
self.yourFollowing.removeAll()
self.following.removeAll()
self.followingTableView.reloadData()
Database.database().reference().child("Businesses").child((loggedInUser?.uid)!).child("following").observe(.value, with: { snapshot in
if snapshot.exists() {
MBProgressHUD.showAdded(to: self.view, animated: true)
let databaseRef = Database.database().reference()
databaseRef.child("Businesses").queryOrderedByKey().observeSingleEvent(of: .value, with: { (usersSnapshot) in
let users = usersSnapshot.value as! [String: AnyObject]
for (_, value) in users {
if let userID = value["uid"] as? String {
if userID == Auth.auth().currentUser?.uid {
print(value)
if let followingUsers = value["following"] as? [String : String] {
for (_,user) in followingUsers {
self.following.append(user)
}
}
databaseRef.child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { (postsSnapshot) in
let posts = postsSnapshot.value as! [String: AnyObject]
for (_, post) in posts {
for (_, postInfo) in post as! [String: AnyObject] {
if let followingID = postInfo["uid"] as? String {
for each in self.following {
if each == followingID {
guard let uid = postInfo["uid"] as! String? else {return}
guard let name = postInfo["businessName"] as! String? else {return}
guard let address = postInfo["businessStreet"] as! String? else {return}
guard let state = postInfo["businessState"] as! String? else {return}
guard let city = postInfo["businessCity"] as! String? else {return}
self.yourFollowing.append(Information(uid: uid, businessName: name, businessStreet: address, businessCity: city, businessState: state))
}
self.followingTableView.backgroundView = nil
self.followingTableView.reloadData()
}
}
}
}
MBProgressHUD.hide(for: self.view, animated: true)
}) { (error) in
print(error.localizedDescription)
}
}
}
}
})
} else {
print("Not following anyone")
self.followingTableView.backgroundView = self.noDataView
MBProgressHUD.hide(for: self.view, animated: true)
}
})
}
}
"Information" Data Structure File
import UIKit
class Information {
var uid: String
var businessName: String
var businessStreet: String
var businessCity: String
var businessState: String
init(uid: String, businessName: String, businessStreet: String, businessCity: String, businessState: String){
self.uid = uid
self.businessName = businessName
self.businessStreet = businessStreet
self.businessCity = businessCity
self.businessState = businessState
}
}
The error is pretty clear.
user in ExploreBusinessProfileSwitchView is obviously declared as NSDictionary, declare it as Information.
By the way don't use NSArray / NSDictionary in Swift. Use native types.

Same method works in ViewDidLoad, but doesn't work in custom TableViewCell button action

I am learning iOS swift and creating an application to learn about getting JSON data and saving this data to CoreData while working with Itunes search api. I have a table view and am using a custom table view cell, it has some labels, an image and a download button. My purpose is to able to get album and all songs in that album information to CoreData after clicking the button of the cell. Here is the list of what is working and what is not working:
Clicking the button gives me the correct CollectionId for the album.
The album information is successfully added to CoreData.
I'm NOT able to fill my songs array after calling the api in my download action method. It stays empty. Note that when I call the api in ViewDidLoad with a manually entered collection id, the songs array is filled.
Codes:
API Controller to get the song information.
import Foundation
protocol songAPIControllerForCoreDataProtocol {
func didReceiveAPISongResults(results: NSDictionary)
}
class songAPIControllerForCoreData {
var delegate: songAPIControllerForCoreDataProtocol
init(delegate: songAPIControllerForCoreDataProtocol) {
self.delegate = delegate
}
func searchItunesForSongsBelongingTo(searchTerm: String) {
// The iTunes API wants multiple terms separated by + symbols, so I'm replacing spaces with + signs
let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
// Escape anything else that isn't URL-friendly
if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
// Using Itunes search api to find people that has a music album with the entered search term
let urlPath = "https://itunes.apple.com/lookup?id=\(escapedSearchTerm)&entity=song"
let url: NSURL = NSURL(string: urlPath)!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as! NSDictionary
println(jsonResult[0])
if(err != nil) {
// If there is an error parsing JSON, print it to the console
println("JSON Error \(err!.localizedDescription)")
}
self.delegate.didReceiveAPISongResults(jsonResult)
println(jsonResult)
})
task.resume()
}
}
}
Song class (Not CoreData):
import Foundation
class Song {
var title: String
var previewURL: String
var collectionID: Int
init(title: String, previewURL: String, collectionID: Int) {
self.title = title
self.previewURL = previewURL
self.collectionID = collectionID
}
class func songsWithJSON(allResults: NSArray) -> [Song] {
// Create an empty array of Albums to append to from this list
var songs = [Song]()
// Store the results in our table data array
if allResults.count>0 {
// Sometimes iTunes returns a collection, not a track, so we check both for the 'name'
for result in allResults {
var title = result["trackName"] as? String
if title == nil {
title = result["collectionName"] as? String
}
if title == nil {
title = result["collectionName"] as? String
}
let previewURL = result["previewUrl"] as? String ?? ""
let collectionID = result["collectionId"] as? Int ?? 0
var newSong = Song(title: title!, previewURL: previewURL, collectionID: collectionID)
songs.append(newSong)
}
}
return songs
}
}
Finally AlbumViewController:
import UIKit
import CoreData
class AlbumViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, searchAPIControllerProtocol, songAPIControllerForCoreDataProtocol {
#IBOutlet
var tableView: UITableView!
#IBOutlet weak var artistNameOutlet: UILabel!
var songapi : songAPIControllerForCoreData?
var api : searchAPIController?
var albums = [Album]()
var songs = [Song]()
var imageCache = [String : UIImage]()
//Variables that take the values after segue from uTableViewController
var artistID, artistName: String?
let cellIdentifier: String = "albumCell"
//for CoreData
var error:NSError?
let managedObjectContext = (UIApplication.sharedApplication().delegate
as! AppDelegate).managedObjectContext
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.albums.count
}
func download(sender: AnyObject){
var senderButton : UIButton = sender as! UIButton
let newAlbum = NSEntityDescription.insertNewObjectForEntityForName("Albums", inManagedObjectContext: managedObjectContext!) as! Albums
let newSong = NSEntityDescription.insertNewObjectForEntityForName("Songs", inManagedObjectContext: managedObjectContext!) as! Songs
songapi!.searchItunesForSongsBelongingTo((String)(self.albums[senderButton.tag].collectionID))
newAlbum.albumArt = self.albums[senderButton.tag].largeImageURL
newAlbum.albumID = (String)(self.albums[senderButton.tag].collectionID)
newAlbum.albumName = self.albums[senderButton.tag].title
newAlbum.albumPrice = self.albums[senderButton.tag].price
newAlbum.artistID = self.artistID!
newAlbum.artistName = self.artistName!
newAlbum.numberOfSongs = (String)(self.albums[senderButton.tag].trackCount)
newAlbum.has = []
println(self.songs)
for(var i = 1; i < self.albums[senderButton.tag].trackCount - 1; i++){
newSong.collectionID = String(self.songs[i].collectionID)
newSong.previewURL = self.songs[i].previewURL
newSong.songName = self.songs[i].title
}
self.managedObjectContext?.save(&self.error)
println(newAlbum)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: AlbumTableViewCell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! AlbumTableViewCell
cell.albumCellButton.tag = indexPath.row
cell.albumCellButton.addTarget(self, action: "download:", forControlEvents: .TouchUpInside)
let album = self.albums[indexPath.row]
cell.albumName.text = album.title
cell.artistImage.image = UIImage(named: "user7.png")
cell.numberOfSongs.text = (String)(album.trackCount) + " Songs"
// Get the formatted price string for display in the subtitle
let formattedPrice = album.price
// Grab the artworkUrl60 key to get an image URL for the app's thumbnail
let urlString = album.thumbnailImageURL
// Check our image cache for the existing key. This is just a dictionary of UIImages
var image = self.imageCache[urlString]
if( image == nil ) {
// If the image does not exist, we need to download it
var imgURL: NSURL = NSURL(string: urlString)!
// Download an NSData representation of the image at the URL
let request: NSURLRequest = NSURLRequest(URL: imgURL)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
if error == nil {
image = UIImage(data: data)
// Store the image in to our cache
self.imageCache[urlString] = image
dispatch_async(dispatch_get_main_queue(), {
if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) as?AlbumTableViewCell {
cellToUpdate.artistImage.image = image
}
})
}
else {
println("Error: \(error.localizedDescription)")
}
})
}
else {
dispatch_async(dispatch_get_main_queue(), {
if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) as?AlbumTableViewCell {
cellToUpdate.artistImage.image = image
}
})
}
cell.priceOfAlbum.text = formattedPrice
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
func didReceiveAPIResults(results: NSDictionary) {
var resultsArr: NSArray = results["results"] as! NSArray
dispatch_async(dispatch_get_main_queue(), {
self.albums = Album.albumsWithJSON(resultsArr)
self.tableView!.reloadData()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
})
}
func didReceiveAPISongResults(results: NSDictionary) {
var resultsArr: NSArray = results["results"] as! NSArray
dispatch_async(dispatch_get_main_queue(), {
self.songs = Song.songsWithJSON(resultsArr)
self.tableView!.reloadData()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
})
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = artistName
artistNameOutlet.text = " Albums"
api = searchAPIController(delegate: self)
songapi = songAPIControllerForCoreData(delegate: self)
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
api!.searchItunesForAlbumsBelongingTo(self.artistName!, id: self.artistID!)
// Do any additional setup after loading the view.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let songsController = segue.destinationViewController as! SongsViewController
var albumCollectionID = self.albums
var albumIndex = tableView!.indexPathForSelectedRow()!.row
var collectionID = self.albums[albumIndex].collectionID
var albumName = self.albums[albumIndex].title
songsController.albumName = albumName
songsController.collectionID = collectionID
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You need to write the definition of your protocol like follows:
protocol songAPIControllerForCoreDataProtocol : class {
func didReceiveAPISongResults(results: NSDictionary)
}
This will make it class only protocol and will force the confirming type to have reference semantics. If no 'class' keyword is specified it will have value semantics.
Without the 'class' keyword the issue here I assume is setting the delegate via initializer. When you pass delegate like:
songapi = songAPIControllerForCoreData(delegate: self)
This will assume the delegate param to be on value type and copy the value rather than send a reference of it. So when you set that value in init() the delegate member will point to a new object rather than the UIViewController passed.
If you set the delegate like:
songapi.delegate = self
it will work without the 'class' keyword in protocol definition.

Resources