UICollectionView Memory Leak (with UIWebView or UIImageView inside cells) - swift

In my app, I have a UICollectionView with some of cells with images from web and some with Youtube videos via UIWebView. When I press Back via NavigationController memory seems to add up more and more every time. I tried disabling video player, it helped a bit, but still..
CollectionViewController
var listOfData = [SingleData]()
var Data:[[String:AnyObject]]?{
didSet{
for asset in Data! {
let info = SingleData.init(Data: asset)
listOfData.append(info)
}
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
listOfData.removeAll()
Data?.removeAll()
imageCache.removeAllObjects()
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return listOfData.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Data Single Cell", forIndexPath: indexPath) as! CollectionViewControllerCell
cell.oneData = listOfData[indexPath.item]
return cell
}
CollectionViewControllerCell
#IBOutlet var videoWebView: UIWebView!
#IBOutlet var singleDataImageView: UIImageView!
var oneData:SingleData! {
didSet{
updateUI()
}
}
func updateUI(){
singleImageView.image = nil
if (oneData.isVideo == true) {
videoWebView.hidden = false
singleDataImageView.hidden = true
showVideo()
} else {
videoWebView.hidden = true
singleDataImageView.hidden = false
setImage(oneData.imageURL, singleDataImageView)
}
}
func showVideo() {
if let videoUrl = oneData.videoUrl {
let replacedString = videoUrl.replace("watch?v=", withString: "embed/")
videoWebView.scrollView.scrollEnabled = false
videoWebView.scrollView.bounces = false
videoWebView.allowsInlineMediaPlayback = true
let embededHTML = "<html><head><title>.</title><style>body,html,iframe{margin:0;padding:0;}</style></head><body><iframe width=\"\(videoWebView.frame.width)\" height=\"\(videoWebView.frame.height)\" src=\"\(replacedString)?&playsinline=1\" frameborder=\"0\" allowfullscreen></iframe></body></html>"
videoWebView.loadHTMLString(embededHTML, baseURL:nil)
}
}
func setImage(url : String?, _ imageView : UIImageView) {
imageView.image = nil
imageUrlString = url
if let cUrl = url {
if let imageFromCache = imageCache.objectForKey(cUrl) as? UIImage {
imageView.image = imageFromCache
return
}
if let urlImage = NSURL(string: cUrl) {
Request.sharedInstance.session.dataTaskWithURL(urlImage, completionHandler: { (data, response, error) in
if error != nil {
print(error)
return
}
dispatch_async(dispatch_get_main_queue(), {
let imageToCache = UIImage(data: data!)
if self.imageUrlString == url {
imageView.image = imageToCache
}
imageCache.setObject(imageToCache!, forKey: url!)
});
}).resume()
}
}
}
SingleData
class SingleData {
var imageURL:String? = nil
var videoUrl:String? = nil
var isVideo:Bool? = false
var dataAssets:Dictionary<String, AnyObject>?
init(Data : Dictionary<String, AnyObject>?){
dataAssets = Data
if Data!["MediaContent"]!["MediaType"] as? Int == 2 {
isVideo = true
}
imageURL = dataAssets!["MediaContent"]!["FileUrl"] as? String
videoUrl = dataAssets!["MediaContent"]!["FileName"] as? String
}
}

Related

Adding image from api to CustomCell programmatically is getting laggy

My tableview gets slow when scrolling, I have a custom cell and a tableview:
I have this controller, where the api call is made and the array of trips is filled, then in cellForRowAt im creating the cell
class HistorialViewController: UIViewController , UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var historialTableView: UITableView!
var trips = [RootClass]()
override func viewDidLoad() {
super.viewDidLoad()
self.historialTableView.delegate = self
self.historialTableView.dataSource = self
self.historialTableView.register(CustomCellHistorial.self, forCellReuseIdentifier: cellId)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("coming back")
self.fetchTrips()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.historialTableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! CustomCellHistorial
let trip = self.trips[indexPath.row]
cell.trip = trip
return cell
}
private func fetchTrips(){
AFWrapper.getTripHistory( success: { (jsonResponse) in
self.trips = []
for item in jsonResponse["trips"].arrayValue {
self.trips.append(RootClass(fromJson:item))
}
self.reloadTrips()
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (nil) in
self.indicator.stopAnimating()
}
}, failure: { (error) -> Void in
print(error)
})
}
func reloadTrips(){
DispatchQueue.main.async{
self.historialTableView.reloadData()
}
}
This is my CustomCell
class CustomCellHistorial : UITableViewCell{
var trip: RootClass? {
didSet{
dateTimeLabel.text = returnCleanDate(fecha: trip!.createdAt)
distanceAndTimeLabel.text = returnDistanceAndTime(distance: (trip?.trip!.real!.dist!)!, time: (trip?.trip!.real!.time)!)
priceLabel.text = returnCleanPrice(price: (trip?.trip!.real!.price!)!)
ratedLabel.text = "Not Rated"
self.productImage.image = self.returnDriverImage(photoUrl: (self.trip?.driver!.photo!)!)
if (trip!.score) != nil {
let score = trip!.score
if (score?.driver) != nil{
if(trip!.score!.driver!.stars! != 0.0 ){
ratedLabel.isHidden = true
}else{
ratedLabel.isHidden = false
}
}else{
print("yei")
}
}
}
}
private func returnDriverImage(photoUrl: String) -> UIImage{
let url = URL(string: photoUrl)
do {
let data = try Data(contentsOf: url!)
if let roundedimg = UIImage(data: data){
let croppedImageDriver = roundedimg.resize(toTargetSize: self.productImage.frame.size)
return croppedImageDriver
}
} catch let error {
debugPrint("ERRor :: \(error)")
let image = UIImage(named: "perfilIcono")
return image!
}
let image = UIImage(named: "perfilIcono")
return image!
}
Answers that I have found are for older versions of Swift, and the way they make the tableview its in storyboard or they are not handling custom cells.
I think the problem is in the returnDriverImage function.
This line
let data = try Data(contentsOf: url!)
You call from
self.productImage.image = self.returnDriverImage(photoUrl: (self.trip?.driver!.photo!)!)
blocks the main thread and re downloads the same image multiple times when scroll , please consider using SDWebImage

Reloading the tableview inside a ViewController getting me ' Fatal error: index out of range' , in cellforRow at index path

When I open the view it opens up quickly and doesn't show any errors but when I reopen it, it gives me 'fatal error'. This is a tableview inside the ViewController which is getting me the data but when I reopen the view it gives error.
The tableview is embedded inside the view controller when I call the tableview again it gives fatal error index out of range inside the ViewController. This needs to be solved I think issue is in my number of rows in section returning the array.
import UIKit
import Alamofire
import Foundation
protocol Quantitypass {
func qtypass(qty: String, product_id: String)
}
public struct QtyCart {
var qty : String!
public init(qty: String) {
self.qty = qty
}
}
public struct FavouriteCart {
var p_name : String!
var p_price : String!
var p_id : String!
var qty: String!
public init(p_name: String , p_price: String , p_id : String, qty: String) {
self.p_name = p_name
self.p_price = p_price
self.p_id = p_id
self.qty = qty
}
}
public struct favSection {
var favitems: [FavouriteCart]
public init(favitems: [FavouriteCart] ) {
self.favitems = favitems
}
}
public struct MyVariables {
static var product2 = "p_id"
}
public var cartData: [favSection] = []
class newCartViewController: UIViewController {
#IBAction func btnSubmit(_ sender: UIButton) {
self.loadnextVC()
}
func loadnextVC() {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "finalVC")
self.present(newViewController, animated: true, completion: nil)
}
#IBOutlet weak var lblPreorder: UILabel!
#IBOutlet weak var lblOrdermethod: UILabel!
#IBOutlet weak var lblTotalAmount: UILabel!
// public var itemsData: [Item] = []
var lblOrder = String()
// let productPrice = UserDefaults.standard.object(forKey: "p_price")
let cartid = UserDefaults.standard.object(forKey: "deviceUUID") as? String
let getApi = RestaurantAPI.self
var qtyData: [QtyCart] = []
// var currentSection = 0
// var currentRow = 1
// var calculation1 = cartTableViewCell()
#IBOutlet weak var tableView: UITableView!
#IBAction func dismiss(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.lblTotalAmount.text = ""
}
//public var cartData: [FavouriteCart] = []
var refreshControl = UIRefreshControl()
var buttonClicked = statusViewController.self
var btn2 = UserDefaults.standard.object(forKey:"button1") as? Bool
var btn3: Bool = true
weak var delegate: DetailsDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.getTotal1()
print("#------/\(cartData.count)")
//self.updateLabel(withString: lblTotalAmount.text)
if btn2 != btn3 {
lblPreorder.isHidden = true
lblOrdermethod.isHidden = false
print("BUTTONCLICKED")
UserDefaults.standard.removeObject(forKey: "button1")
} else if btn2 == btn3 {
lblPreorder.isHidden = false
lblOrdermethod.isHidden = true
print("BUTTON-NOT-CLICKED")
UserDefaults.standard.removeObject(forKey: "button1")
}
// if #available(iOS 10.0, *) {
// tableView.refreshControl = refreshControl
// } else {
// tableView.addSubview(refreshControl)
//
// }
// self.refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
// self.refreshControl.addTarget(self, action: #selector(newCartViewController.refreshData), for: UIControlEvents.valueChanged)
//
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.view.addSubview(self.tableView)
self.tableView.dataSource = self
self.tableView.delegate = self
self.getTotal1()
self.getFav()
self.tableView.reloadData()
// self.updateTableview()
// self.getTotal1()
}
func getFav() {
getFav(completionHandler: { success in
if success {
DispatchQueue.main.async{
self.tableView.reloadData()
}
}
})
}
//TOTAL API CALL:
func getTotal1() {
if cartid != nil {
let request = getApi.getamountcartGetWithRequestBuilder(restId: "17", cartId: cartid!)
Alamofire.request(request.URLString, method: .get , parameters: nil, encoding: JSONEncoding.default)
.responseJSON { response in
print("123321#######/\(response)")
let res = response
print("101res/\(res)")
if let value = response.value as? [String: AnyObject] {
if let success = value["error"] as? Bool {
if success == false {
// var grandtotal: Any? = value["total"]
if let grandtotal = value["total"] as? Double {
self.lblTotalAmount.text = String(grandtotal)
print("!#/\(String(grandtotal))")
var g = String(grandtotal)
UserDefaults.standard.set(g, forKey: "g")
}
// self.tableView.reloadData()
}
}
}
}
}
}
//
// Mark: getting all cart items:-->
func getFav(completionHandler: #escaping (Bool) -> Void){
if cartid != nil {
let request = getApi.displaycartGetWithRequestBuilder(restId: "17", cartId:cartid!)
Alamofire.request(request.URLString, method: .get , parameters: nil, encoding: JSONEncoding.default)
.responseJSON { response in
print("123321/\(response)")
let res = response
print("101res/\(res)")
// let total = cartData
// print("cartd1/\(total)")
if let value = response.value as? [String: AnyObject] {
if let success = value["error"] as? Bool {
if success == false {
print("2222/\(response)")
if let response = value["cartdata"] as? [String: AnyObject] {
let cart = response["simple"] as! [[String: AnyObject]]
// let total = value["total"] as! [String: Any]
// print("1231231231234/\(total)")
//let userdata: [Favourites] = []
//
var cartitems: [FavouriteCart] = []
for (_ , value) in cart.enumerated() {
let obj = value
let p_name = obj["p_name"] as! String
let p_price = obj["p_price"] as! String
let p_id = obj["p_id"] as! String
let qty = obj["qty"] as! String
// UserDefaults.standard.set(qty, forKey: "qty")
let item = FavouriteCart(p_name: p_name, p_price: p_price, p_id: p_id, qty: qty)
cartitems.append(item)
DispatchQueue.main.async{
print("RELOADED-----ON-----API")
cartData.append(favSection(favitems: cartitems))
self.tableView.reloadData()
}
}
}
print("COMPLETION-------------HANDLER")
completionHandler(true)
}
}
else
{
let myAlert = UIAlertController(title:"Alert",message:value["error_msg"] as? String,preferredStyle:UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title:"OK",style:UIAlertActionStyle.default , handler: nil)
myAlert.addAction(okAction)
}
}
}
}
}
// Mark:--> Delete items from cart.
// func delFav(p_id: String ,completionHandler: #escaping (Bool) -> Void) {
func delFav(p_id: String, completionHandler: #escaping (Bool) -> Void){
//var product1 = UserDefaults.standard.object(forKey: "p_id")
//print("this is my \(product1)")
let request = getApi.deleteproductcartGetWithRequestBuilder(restId: "17", cartId: cartid!, productId: p_id , freeDish: "none", type: "simple")
Alamofire.request(request.URLString, method: .delete , parameters: nil, encoding: JSONEncoding.default)
.responseJSON { response in
print("del favvvvvvvvv\(response)")
print("<<DELETED ------------ ITEM>>")
//print(response)
if let value = response.value as? [String: AnyObject] {
if let success = value["error"] as? Bool {
if success == false {
let response = value["cartdata"] as? [String]
print("10001 - > /\(response)")
//self.tableView.reloadData()
} else
{
print("error message")
}
}
}
// completionHandler(true)
}
}
func delterow(p_id: String) {
self.delFav(p_id: p_id, completionHandler: {sucess in
if sucess {
self.tableView.reloadData()
print("DELETED_ITEM")
}
})
}
func updateTotal(withString string: String?) {
lblTotalAmount.text = string
}
}
extension newCartViewController: UITableViewDelegate , UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90;
}
// func numberOfSections(in tableView: UITableView) -> Int {
// return cartData.count
// }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// return sectionsData[section].items.count
// return cartData[section].favitems.count
// return cartData[section]..count
return cartData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cartTablecell", for: indexPath) as! cartTableViewCell
let item: FavouriteCart = cartData[indexPath.section].favitems[indexPath.row]
cell.btnsub1.tag = indexPath.row
cell.btnadd1.tag = indexPath.row
cell.lblItemName.text = item.p_name
cell.productPrice.text = item.p_price
cell.lblprice.text = item.p_price
cell.lblQuantity.text = item.qty
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool{
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
if cartData.isEmpty{
print("EmptyCart")
}else {
print("0-0/\(cartData.count)")
// let item: Item = sectionsData[indexPath.section].items[indexPath.row]
let item: FavouriteCart = cartData[indexPath.section].favitems[indexPath.row]
self.tableView.beginUpdates()
cartData.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .fade)
self.delterow(p_id: item.p_id)
self.getTotal1()
self.tableView.endUpdates()
}
}
}
}
It was giving the error because the rows were saved at local but I was calling the data again changes are :
have to delete the whole cart when close the view
#IBAction func dismiss(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.lblTotalAmount.text = ""
cartData.removeAll()
}
I think self.tableView.dataSource = self & self.tableView.delegate = self should be in viewDidLoad method. Adding it to viewDidLoad might work.

How to update table view

I used search bar. It isn't updating the table view.
struct ApiResults:Decodable {
let resultCount: Int
let results: [Music]
}
struct Music:Decodable {
let trackName: String?
let artistName: String?
let artworkUrl60: String?
}
class ItunesDataViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
var musicArray:[Music] = []
var mArray:[Music] = []
var filteredData:[Music] = []
var isSearching = false
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
self.searchBar.delegate = self
searchBar.placeholder = "search"
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String)
{
print("\n\nsearchText : \(searchText)\n\n")
Search(searchTerm: "\(searchText)")
if searchBar.text == nil || searchBar.text == ""
{
isSearching = false
view.endEditing(true)
self.tableView.reloadData()
}
else
{
isSearching = true
filteredData = mArray.filter{$0.artistName == searchText}
self.tableView.reloadData()
}
}
func Search(searchTerm: String)
{
guard let url = URL(string: "https://itunes.apple.com/search?term=\(searchTerm)&attribute=actorTerm&attribute=languageTerm&attribute=allArtistTerm&attribute=tvEpisodeTerm&attribute=shortFilmTerm&attribute=directorTerm&attribute=releaseYearTerm&attribute=titleTerm&attribute=featureFilmTerm&attribute=ratingIndex&attribute=keywordsTerm&attribute=descriptionTerm&attribute=authorTerm&attribute=genreIndex&attribute=mixTerm&attribute=allTrackTerm&attribute=artistTerm&attribute=composerTerm&attribute=tvSeasonTerm&attribute=producerTerm&attribute=ratingTerm&attribute=songTerm&attribute=movieArtistTerm&attribute=showTerm&attribute=movieTerm&attribute=albumTerm") else {return}
URLSession.shared.dataTask(with: url){(data, response, error) in
guard let data = data else {return}
do
{
let apiressults = try JSONDecoder().decode(ApiResults.self, from: data)
for item in apiressults.results
{
if let track_Name = item.trackName, let artist_Name = item.artistName, let artwork_Url60 = item.artworkUrl60
{
let musics = Music(trackName: track_Name, artistName: artist_Name, artworkUrl60: artwork_Url60)
self.musicArray.append(musics)
print(musics.artistName!,"-", musics.trackName!)
}
}
DispatchQueue.main.async
{
self.tableView.reloadData()
}
}
catch let jsonError
{
print("Error:", jsonError)
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isSearching
{
return filteredData.count
}
else
{
return mArray.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "musicCell", for: indexPath) as! ItunesDataTableViewCell
if isSearching
{
cell.lblDesc?.text = filteredData[indexPath.row].artistName
cell.lblSongDesc?.text = filteredData[indexPath.row].trackName
let imgString = filteredData[indexPath.row].artworkUrl60!
let imgUrl:URL = URL(string: imgString)!
DispatchQueue.global(qos: .userInitiated).async {
let imageData:NSData = NSData(contentsOf: imgUrl)!
DispatchQueue.main.async {
let image = UIImage(data: imageData as Data)
cell.imgArt?.image = image
}
}
}
else
{
cell.lblDesc?.text = mArray[indexPath.row].artistName
cell.lblSongDesc?.text = mArray[indexPath.row].trackName
let imgString = mArray[indexPath.row].artworkUrl60!
let imgUrl:URL = URL(string: imgString)!
DispatchQueue.global(qos: .userInitiated).async {
let imageData:NSData = NSData(contentsOf: imgUrl)!
DispatchQueue.main.async {
let image = UIImage(data: imageData as Data)
cell.imgArt?.image = image
}
}
}
return cell
}
}
Please clean up your code 😉: You have two different arrays mArray and musicArray.
You are populating musicArray in Search but mArray is used as data source.
Why do you create new Music items from Music items? You can reduce the code to
let apiressults = try JSONDecoder().decode(ApiResults.self, from: data)
self.mArray = apiressults.results
DispatchQueue.main.async {
self.tableView.reloadData()
}
Please change your code in the cellForRowAt delegate method to:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "musicCell", for: indexPath) as! ItunesDataTableViewCell
let tempArray: [Music] = isSearching ? filteredData : musicArray
cell.lblDesc?.text = tempArray[indexPath.row].artistName
cell.lblSongDesc?.text = tempArray[indexPath.row].trackName
guard let imgString = tempArray[indexPath.row].artworkUrl60,
let imgUrl = URL(string: imgString) else {
// Handle properly the fact that there's no image to display
return cell
}
// Review this code as I'm not sure about this double dispatch
// However, please, no force unwrap optionals (!)
DispatchQueue.global(qos: .userInitiated).async {
do {
let imageData = try Data(contentsOf: imgUrl)
DispatchQueue.main.async {
let image = UIImage(data: imageData)
cell.imgArt?.image = image
}
} catch let error {
print("Error with the image URL: ", error)
}
}
return cell
}
See how you don't repeat your code that way?
Furthermore you were not using the right music array, or we don't have all the information to assess what is wrong with this mix of mArray and musicArray.

Implement UISearchBarController within UICollectionViewController (Swift 4)?

I'm trying to implement an UISearchBarController in UICollectionViewController, here are the codes:
class FirstViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UISearchResultsUpdating {
var titles: [String] = []
var card: [RecommendArticles]?
var searchArr = [String](){
didSet {
self.collectionView?.reloadData()
}
}
func updateSearchResults(for searchController: UISearchController) {
guard let searchText = searchController.searchBar.text else {
return
}
searchArr = titles.filter { (title) -> Bool in
return title.contains(searchText)
}
}
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
navigationItem.hidesSearchBarWhenScrolling = true
self.navigationItem.searchController = searchController
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if (searchController.isActive) {
return searchArr.count
} else {
return self.counters
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: verticalCellId, for: indexPath) as! VerticalCellId
if (searchController.isActive) {
cell.titleLabel.text = searchArr[indexPath.row]
return cell
} else {
if let link = self.card?[indexPath.item]._imageURL {
let url = URL(string: link)
cell.photoImageView.kf.setImage(with: url)
}
if let title = self.card?[indexPath.item]._title {
cell.titleLabel.text = title
titles.append(title)
print("\(titles)")
}
if let source = self.card?[indexPath.item]._source {
cell.sourceLabel.text = source
}
return cell
}
}
Here are the errors locate:
titles.append(title)
print("\(titles)")
When I search a keyword, the filtered results are incorrect, because the collection view cell will change dynamically (I'm not sure if my expression is accurate).
But, if I set up the variable titles like this, it works perfectly:
var titles = ["Tom","Jack","Lily"]
But I have to retrieve the values of titles from internet. Any suggestions would be appreciated.
Here are the update question for creating an array after self.card:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.view.addSubview(activityView)
activityView.hidesWhenStopped = true
activityView.center = self.view.center
activityView.startAnimating()
let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
dynamoDbObjectMapper.load(TheUserIds2018.self, hashKey: "TheUserIds2018", rangeKey: nil, completionHandler: { (objectModel: AWSDynamoDBObjectModel?, error: Error?) -> Void in
if let error = error {
print("Amazon DynamoDB Read Error: \(error)")
return
}
DispatchQueue.main.async {
if let count = objectModel?.dictionaryValue["_articleCounters"] {
self.counters = count as! Int
self.collectionView?.reloadData()
self.updateItems()
self.activityView.stopAnimating()
}
}
})
}
func updateItems() {
let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
var tasksList = Array<AWSTask<AnyObject>>()
for i in 1...self.counters {
tasksList.append(dynamoDbObjectMapper.load(RecommendArticles.self, hashKey: "userId" + String(self.counters + 1 - i), rangeKey: nil))
}
AWSTask<AnyObject>.init(forCompletionOfAllTasksWithResults: tasksList).continueWith { (task) -> Any? in
if let cards = task.result as? [RecommendArticles] {
self.card = cards
DispatchQueue.main.async {
if let totalCounts = self.card?.count {
for item in 0...totalCounts - 1 {
if let title = self.card?[item]._title {
self.newTitle = title
}
self.titles.append(self.newTitle)
print("titles: \(self.titles)")
}
}
}
} else if let error = task.error {
print(error.localizedDescription)
}
return nil
}
}
This is not an issue with UISearchController . it is with your data. Collection view data source method -
cellForItemAtIndexPath works only when cell is presented in the view. So you can't keep the code for creating array of titles in the cellForItemAtIndexPath
Instead of separately keeping title strings in array for search you can directly filter self.card array with search text in title with below code
self.searchArr = self.card?.filter(NSPredicate(format:"_title CONTAINS[cd] %#",searchText)).sorted(byKeyPath: "_title", ascending: true)
or
you can create array of titles before loading collection view
Some where after you create self.card

Wrong indexPathForSelectedRow when using prepareForSegue

I'm trying to fill a tableView with some products from my MySQL database (using a PHP POST file) and at the moment everything is fine, but when I select a "Cell", the prepareForSegue is triggered, but the indexPathForSelectedRow is wrong, so it's showing a different product.
Here is my full code, I hope you can tell me something because I don't know why is this happening, I have tried a lot of things and I'm out of options...!
TableViewController.swift
import UIKit
class ListadoBuscarResultadosTableViewController: UITableViewController {
var option: String = ""
var productos = [Producto]()
var refreshControl2:UIRefreshControl!
var imageCache = [String:UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
requestPost()
title = self.option
tableView.allowsMultipleSelection = true
tableView.scrollsToTop = true
self.tableView.reloadData()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.hidesBarsOnSwipe = false
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
func refresh(sender:AnyObject) {
requestPost()
self.refreshControl2.endRefreshing()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return productos.count
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
// Define the initial state (Before the animation)
cell.alpha = 0.25
// Define the final state (After the animation)
UIView.animateWithDuration(1.0, animations: { cell.alpha = 1 })
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// try to reuse cell
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! BuscarCellTableViewCell
cell.selectionStyle = .None
//cell.nombre.text = productos[indexPath.row].nombre
//cell.marca.text = productos[indexPath.row].marca
//println(cell.nombre.text)
// get the deal image
let currentImage = productos[indexPath.row].imagen
let unwrappedImage = currentImage
var image = self.imageCache[unwrappedImage]
let imageUrl = NSURL(string: productos[indexPath.row].imagen)
// reset reused cell image to placeholder
cell.imagen.image = UIImage(named: "")
// async image
if image == nil {
let request: NSURLRequest = NSURLRequest(URL: imageUrl!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
if error == nil {
image = UIImage(data: data)
self.imageCache[unwrappedImage] = image
dispatch_async(dispatch_get_main_queue(), {
cell.imagen.image = image
cell.nombre.text = self.productos[indexPath.row].nombre
cell.marca.text = self.productos[indexPath.row].marca
})
}
else {
cell.nombre.text = self.productos[indexPath.row].nombre
cell.marca.text = self.productos[indexPath.row].marca
}
})
}
else {
cell.imagen.image = image
cell.nombre.text = self.productos[indexPath.row].nombre
cell.marca.text = self.productos[indexPath.row].marca
}
return cell
}
func requestPost () {
let request = NSMutableURLRequest(URL: NSURL(string: "http://www.web.es/productos_by_category.php")!)
request.HTTPMethod = "POST"
let postString = "category="+option
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
//println("error=\(error)")
return
}
let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)!
// JSON RESULTADO ENTERO
//println("responseString = \(responseString)")
self.productos = self.parseJsonData(data)
// Reload table view
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
task.resume()
}
func parseJsonData(data: NSData) -> [Producto] {
var productos = [Producto]()
var error:NSError?
let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as? NSDictionary
// Return nil if there is any error
if error != nil {
println(error?.localizedDescription)
}
// Parse JSON data
let jsonProductos = jsonResult?["lista_productos"] as! [AnyObject]
for jsonProducto in jsonProductos {
let producto = Producto()
producto.nombre = jsonProducto["nombre"] as! String
producto.imagen = jsonProducto["imagen"] as! String
producto.marca = jsonProducto["marca"] as! String
producto.distribuidor = jsonProducto["distribuidor"] as! String
producto.linea = jsonProducto["linea"] as! String
producto.precio = jsonProducto["precio"] as! String
productos.append(producto)
}
return productos
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "verProducto" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let destinationController = segue.destinationViewController as! MarcaProductoViewController
//println(productos[indexPath.row].nombre)
println(indexPath)
destinationController.nombre = productos[indexPath.row].nombre
}
}
}
Thanks in advance,
Regards.
try this way...
if segue.identifier == "verProducto"{
if let indexPath = tableView.indexPathForCell(sender as! BuscarCellTableViewCell){
var detailsVC = segue.destinationViewController as! MarcaProductoViewController
}
}