In my project has 2 ViewControllers (ViewController and DetailViewController). On the first there are table view in UIScrollview. On the second - buttons in UICollectionView with images and links. When i popup in DetailViewController many times, the app starts slow down, and i see the leak memory on graph. Also in Debug memory i see 18 object of CollectionViewCell and 3 DetailViewController, First leak of CellProps i solve by:
override func viewDidDisappear(_ animated: Bool) {
btnArray.removeAll()
How to solve this memory leak?
ViewController.swift
import UIKit
import BetterSegmentedControl
import Firebase
class ViewController: UIViewController, UIScrollViewDelegate, UITableViewDelegate {
var ICOListGoingOn = [ICOs] ()
var ICOListEnded = [ICOs] ()
var ICOListnotstarted = [ICOs] ()
#objc func reload(n: NSNotification) {
SaveLikedArray()
self.icoArraySort()
let slide = SlideView(frame: CGRect(x: view.frame.width * CGFloat(0), y: 0, width: view.frame.width, height: slideScrollView.frame.height))
slide.ICO = ICOListLiked
slide.delegate = self
slideScrollView.addSubview(slide)
}
extension ViewController: SlideViewDelegate{
func tableCellSelected(tableView: UITableView, indexPath: IndexPath, ico: ICOs) {
// print("Table tag : \(tableView.tag) Selcted Row : \(indexPath.row) Selected Value : \(ico)")
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DetailViewController") as? DetailViewController{
controller.ICO = ico
ICOId = ico.ICOId
self.navigationController?.pushViewController(controller, animated: true)
}
}
}
CollectionViewCell.swift
import UIKit
class CellProps {
var image: UIImage!
var url: String!
init (image: UIImage, url: String) {
self.image = image
self.url = url
}
deinit {
print("CellProps")
}
}
protocol CollectionViewCellDelegate {
func didButtonClick(url: String)
}
class CollectionViewCell: UICollectionViewCell {
var delegate: CollectionViewCellDelegate!
weak var CellItem: CellProps!
#IBOutlet weak var btnICONOutlet: UIButton!
#IBAction func btnICONAction(_ sender: Any) {
delegate?.didButtonClick(url: CellItem.url)
}
func setCell(cell: CellProps) {
CellItem = cell
CellItem.url = cell.url
btnICONOutlet.setImage(cell.image, for: .normal)
}
deinit {
print("CollectionViewCell")
}
}
DetailViewController.swift
import UIKit
class DetailViewController: UIViewController {
var ICO: ICOs!
var btnArray: [CellProps] = []
var timer: Timer?
#IBOutlet weak var outCollectioView: UICollectionView!
#IBOutlet weak var btnLike: LikeButton!
#IBAction func btnLikeAction(_ sender: Any) {
if !btnLike.isOn {
let ind = CountLikedArray(id: ICO.ICOId)
if ind != -1 {
ICOListLiked.remove(at: ind)
lblLike.text = "Вы еще не лайкнули проект"
}
}
if btnLike.isOn {
let ind = CountLikedArray(id: ICO.ICOId)
if ind == -1{
ICOListLiked.append(self.ICO)
lblLike.text = "Вам нравится проект"
}
}
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "del"), object: nil)
}
func FillButtonArray () {
for value in ICO.news.values {
var btn : CellProps!
if value.lowercased().range(of:"t.me") != nil {
btn = CellProps(image: #imageLiteral(resourceName: "telegram"), url: value)
}
else if value.lowercased().range(of:"bitcointalk.org") != nil {
btn = CellProps(image: #imageLiteral(resourceName: "bitcoin"), url: value)
} else {
btn = CellProps(image: #imageLiteral(resourceName: "link"), url: value)
}
btnArray.append(btn)
}
}
deinit {
print("deinit detail")
}
override func viewDidDisappear(_ animated: Bool) {
btnArray.removeAll()
timer = nil
outCollectioView = nil
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
extension DetailViewController: UICollectionViewDelegate, UICollectionViewDataSource, CollectionViewCellDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return btnArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let ICONs = btnArray[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.setCell(cell: ICONs)
cell.delegate = self
return cell
}
func didButtonClick(url: String) {
let ICONURL = URL (string: url)!
UIApplication.shared.open(ICONURL as URL)
}
}
if i debug memory graph there are 3 DatailViewController, and 18 CollectionViewCell
I found lazy way to solve self of my problem, it's
outCollectioView.removeFromSuperview()
btnLike.removeFromSuperview()
But DetailViewController still increasing (((
Related
I'm trying to create a photo album via UIImagePicker into a CollectionView and cannot get it to segue to that same photo again in a detailed UIViewController. Pulling my hair out and this is just a tutorial as I have just started coding!
Can anyone tell me what I'm doing wrong?
Here is my ViewController:
import UIKit
class ViewController: UIViewController, UINavigationControllerDelegate {
#IBOutlet private weak var addButton: UIBarButtonItem!
#IBOutlet private weak var collectionView:UICollectionView!
#IBOutlet private weak var trashButton:UIBarButtonItem!
var testItems = [Person]()
var stormTrooperCollectionArray: [UIImage] = [#imageLiteral(resourceName: "StormTrooper-3052-423a-8c57-7220081e1585_800x"), #imageLiteral(resourceName: "ST3"), #imageLiteral(resourceName: "ST2"), #imageLiteral(resourceName: "ST4")]
#IBAction func addItem() {
addNewPerson()
}
#IBAction func trashItem(_ sender: Any) {
if let selectedItems = collectionView.indexPathsForSelectedItems {
let itemsForDeletion = selectedItems.map{$0.item}.sorted().reversed()
for item in itemsForDeletion {
testItems.remove(at: item)
}
collectionView.deleteItems(at: selectedItems)
}
}
#objc func refresh() {
addItem()
collectionView.refreshControl?.endRefreshing()
}
override func viewDidLoad() {
super.viewDidLoad()
// Set up a 3-column Collection View
let width = (view.frame.size.width - 20) / 3
let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = CGSize(width:width, height:width)
// Refresh Control
let refresh = UIRefreshControl()
refresh.addTarget(self, action: #selector(self.refresh), for: UIControlEvents.valueChanged)
collectionView.refreshControl = refresh
// Edit
navigationItem.leftBarButtonItem = editButtonItem
navigationController?.isToolbarHidden = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DetailSegue" {
if let dest = segue.destination as? DetailsViewController,
let index = sender as? IndexPath {
dest.detailedImageHi = stormTrooperCollectionArray [index.row]
}
}
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
navigationController?.isToolbarHidden = !editing
addButton.isEnabled = !editing
trashButton.isEnabled = editing
collectionView.allowsMultipleSelection = editing
let indexes = collectionView.indexPathsForVisibleItems
for index in indexes {
let cell = collectionView.cellForItem(at: index) as! CollectionViewCell
cell.isEditing = editing
}
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UIImagePickerControllerDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return stormTrooperCollectionArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
//cell.titleLabel.text = stormTrooperCollectionArray[indexPath.row]
cell.selectionImage.image = stormTrooperCollectionArray[indexPath.row]
cell.isEditing = isEditing
return cell
}
#objc func addNewPerson() {
let picker = UIImagePickerController()
picker.allowsEditing = true
picker.delegate = self
present(picker, animated: true)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
guard let image = info[UIImagePickerControllerEditedImage] as? UIImage else { return }
let imageName = UUID().uuidString
let imagePicture = getDocumentsDirectory()
if let jpegData = UIImageJPEGRepresentation(image, 80) {
try? jpegData.write(to: imagePicture)
}
//let detailedItem = Person(imageHi: imageName)
//testItems.append(detailedItem)
let detailedItem = Person(imageHi: imageName)
//KEEPS THROWING AN ERROR HERE WHICH SAYS: Cannot convert value of type 'String' to expected argument type 'UIImage'
stormTrooperCollectionArray.append(detailedItem)
collectionView?.reloadData()
dismiss(animated: true)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if !isEditing {
performSegue(withIdentifier: "DetailSegue", sender: indexPath)
}
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
}
Here is my VC for the Cell in the CollectionView:
import UIKit
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var selectionImage: UIImageView!
}
Here is my VC for the Detailed View Controller:
import UIKit
class DetailsViewController: UIViewController {
var selection: String!
var detailedImageHi: UIImage!
#IBOutlet private weak var detailsLabel: UILabel!
#IBOutlet private weak var detailedImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
detailsLabel.text = selection
detailedImage.image = detailedImageHi
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here is my VC for the NSObject Swift File:
import UIKit
class Person: NSObject {
var imageHi: UIImage
init(imageHi: UIImage){
self.imageHi = imageHi
}
}
In your code the UIImage is image and not imageName:
guard let image = info[UIImagePickerControllerEditedImage] as? UIImage else { return }
So all you have to do is pass the right argument to the constructor of Person:
let detailedItem = Person(imageHi: image)
imageName is a random string generated via UUID().uuidString, and used in this code to save the image to the documents directory with a random and unique name.
I have a tableview with a textfield in every row.
I need to reload the tableview and programmatically select the row the user had selected.
The user can write what he wants. The data will be deleted when the textfield's editing has ended and added when the textfield has begun editing.
But I get a infinite loop. Please cloud you help me?
My code :
import UIKit
class OptionsItemViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var textFiedlDelegate: UITextField? = nil
var categorySelected: Category?
var options: [String] = []
var nameOptions: [String] = []
var cellSelected: Int = 0
var viewHeight: CGFloat = 0
var selectedRow: IndexPath? = nil
var tableviewNeedToReload: Bool = false
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var keyboardAlwaysShow: UITextField!
#IBOutlet weak var newFeatureButton: UIBarButtonItem!
private let db = DataBase()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.textFiedlDelegate?.delegate = self
self.title = categorySelected!.name
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
db.getItemOptions(predicateFormat: "id == \(self.categorySelected!.id)", completion: { results in
self.categorySelected = results.first!
self.options = self.categorySelected!.options as! [String]
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(false)
self.viewHeight = self.view.frame.size.height
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(false)
var index = 0
while index < self.options.count {
if self.options[index] != "" {
index += 1
} else {
self.options.remove(at: index)
}
db.setCategoryOptions(category: self.categorySelected!, options: self.options, index: cellSelected)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
#IBAction func newFeature(_ sender: Any) {
if self.options.last != "" {
let indexPath: IndexPath = IndexPath(row: self.options.count, section: 0)
self.options.append("")
self.tableView.reloadData()
let cell = tableView(self.tableView, cellForRowAt: indexPath) as! CellItemOptions
cell.nameOptionsItem.becomeFirstResponder()
}
}
// MARK: - TableView Functions
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return options.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let option = options[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: CellItemOptions.identifier, for: indexPath) as! CellItemOptions
cell.nameOptionsItem.delegate = self
cell.configureCell(with: option)
return cell
}
func textFieldDidBeginEditing(_ textField: UITextField) {
self.cellSelected = options.index(of: textField.text!)!
let indexPath: IndexPath = IndexPath(row: self.cellSelected, section: 0)
self.tableView.reloadData()
let cell = self.tableView.cellForRow(at: indexPath) as! CellItemOptions
cell.nameOptionsItem.becomeFirstResponder()
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField.text! == "" {
if self.options[cellSelected] != "" {
db.setRemoveDetailsItem(category: self.categorySelected!, index: cellSelected)
}
self.options.remove(at: cellSelected)
} else {
self.options[cellSelected] = "\(textField.text!)"
db.setAddDetailsItem(category: self.categorySelected!, index: cellSelected)
}
db.setCategoryOptions(category: self.categorySelected!, options: self.options, index: cellSelected)
}
// MARK: - Keyboard
func keyboardWillShow(_ notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.size.height == self.viewHeight {
self.view.frame.size.height -= keyboardSize.height
}
}
}
func keyboardWillHide(_ notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != self.viewHeight {
self.view.frame.size.height += keyboardSize.height
}
}
}
}
class CellItemOptions: UITableViewCell {
static let identifier = "OptionsItemCell"
#IBOutlet weak var nameOptionsItem: UITextField!
private let tableView = OptionsItemViewController()
func configureCell(with cell: String) {
nameOptionsItem.text = cell
}
}
EDIT :
The loop is due to the reload data...
Like I reload data in textFieldDidBeginEditing(), the view is reloaded more and more ... And I need to textFieldDidBeginEditing() to know the row selected by the user.
private let DBItemCellIdentifier = "ItemCellIdentifier"
private let DBItemSegueIdentifier = "ItemSegueIdentifier"
class DBItemsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, DBItemTableViewCellDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var previousButton: UIButton!
#IBOutlet weak var nextButton: UIButton!
#IBOutlet weak var categoryNameLabel: UILabel!
private var elements = [Any]()
private var currentItemIndex = 0
private var isFetching = false
private weak var currentCategory: DBCategory? {
didSet {
updateView()
}
}
var categories = [DBCategory]()
var currentCategoryIndex = 0
//MARK: - Class Methods
//MARK: - Initialization
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100.0
tableView.tableFooterView = UIView(frame: CGRectZero)
setupUserAndCartButtons()
fetchItems()
}
deinit {
print("deinit")
}
//MARK: - Actions
#IBAction func nextButtonTapped(sender: UIButton) {
currentCategoryIndex = min(currentCategoryIndex + 1, categories.count - 1)
fetchItems()
}
#IBAction func previousButtonTapped(sender: UIButton) {
currentCategoryIndex = max(currentCategoryIndex - 1, 0)
fetchItems()
}
//MARK: - Private
private func fetchItems() {
tableView.alpha = 0
currentCategory = nil
if !categories.isEmpty && !isFetching {
let category = categories[currentCategoryIndex]
currentCategory = DBCategory.findCategoryWithIdentifier(category.identifier)
if currentCategory == nil {
SVProgressHUD.show()
}
isFetching = true
DBNetworkClient.sharedClient().itemsForCategory(category, completionBlock: { error in
defer {
self.isFetching = false
SVProgressHUD.dismiss()
UIAlertController.showAlertFromError(error)
}
self.currentCategory = DBCategory.findCategoryWithIdentifier(category.identifier)
})
}
}
private func updateView() {
let category = categories[currentCategoryIndex]
title = category.menu.location.name
categoryNameLabel.text = category.name
previousButton.hidden = currentCategoryIndex == 0 ? true : false
nextButton.hidden = currentCategoryIndex == categories.count - 1 ? true : false
prepareElements()
tableView.reloadData()
UIView.animateWithDuration(0.5, animations: {
self.tableView.alpha = 1
})
}
private func prepareElements() {
elements.removeAll(keepCapacity: false)
if let items = currentCategory?.items {
for item in items {
elements.append(item)
}
}
if let sets = currentCategory?.sets {
for set in sets {
elements.append(set)
}
}
elements.sortInPlace {
let left = ($0 as? DBSet)?.position ?? ($0 as? DBItem)?.position
let right = ($1 as? DBSet)?.position ?? ($1 as? DBItem)?.position
return left < right
}
}
//MARK: - Overridden
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let element = elements[currentItemIndex]
if segue.identifier == DBItemSegueIdentifier {
let itemViewController = segue.destinationViewController as! DBItemViewController
itemViewController.prepareWithElement(element)
}
}
//MARK: - UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0 //when I change to elements.count, deinit is not called
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(DBItemCellIdentifier, forIndexPath: indexPath) as! DBItemTableViewCell
let element = elements[indexPath.row]
if let item = element as? DBItem {
cell.configureCellWithItem(item)
} else if let set = element as? DBSet {
cell.configureCellWithSet(set)
}
cell.delegate = self
return cell
}
//MARK: - UITableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
currentItemIndex = indexPath.row
performSegueWithIdentifier(DBItemSegueIdentifier, sender: tableView.cellForRowAtIndexPath(indexPath))
}
//MARK: - DBItemTableViewCellDelegate
func itemTableViewCell(cell: DBItemTableViewCell, willPresentSetGroupsViewControllerForSet set: DBSet) {
presentSetOrderControllerWithOrder(DBSetOrder(set: set))
}
func itemTableViewCell(cell: DBItemTableViewCell, willPresentItemMealSizesViewControllerForItem item: DBItem) {
presentItemOrderControllerWithOrder(DBItemOrder(item: item))
}
}
Why my deinit is not called. I will offer 100 bounty once I will be able to do this, and award to that one, who help me solve this problem... I will offer a bounty even after solving the problem.
VERY IMPORTANT INFO:
this code calls deinit. IT IS WORKING. Because number of rows is 0. But I need to have there elements.count. When I change to this, deinit is not called.
EDIT:
func itemsForCategory(category: DBCategory, completionBlock: DBErrorHandler) {
let query = "locations/" + category.menu.location.identifier + "/categories/" + category.identifier
GET(query, parameters: nil, success: { operation, response in
if let error = NSError(response: response) {
completionBlock(error)
} else {
self.coreDataAssistant.parseAndSaveItemsToPersistentStore(response as? NSDictionary, completionBlock: { error in
completionBlock(error)
})
}
}) { operation, error in
let responseError = NSError(response: operation.responseObject)
completionBlock(responseError ?? error)
}
}
You are assigning self as your table view cell's delegate:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(DBItemCellIdentifier, forIndexPath: indexPath) as! DBItemTableViewCell
let element = elements[indexPath.row]
if let item = element as? DBItem {
cell.configureCellWithItem(item)
} else if let set = element as? DBSet {
cell.configureCellWithSet(set)
}
// HERE
cell.delegate = self
return cell
}
The cell's delegate property is defined as follows:
var delegate: DBItemTableViewCellDelegate?
This creates a strong reference between the cell and the delegate (your view controller). The cell is also retained by the table view. This creates a retain cycle.
You will need to change the definition of the delegate property to be weak:
weak var delegate: DBItemTableViewCellDelegate?
Edit based on comment:
Your DBItemTableViewCellDelegate definition will need to be defined as a class-only protocol
protocol DBItemTableViewCellDelegate: class {
...
}
In the Swift app, I present an addition view to add an element to the CoreData database. If I call tableview.reloadData(), the app crashes on the + button on the main screen. If I omit the reload data then the add view is presented and the data is added to the CoreData file.
the main view, from configureCell down:
func configureCell(cell: TransectTableViewCell, indexPath:NSIndexPath) {
let transectEntry = fetchedResultController.objectAtIndexPath(indexPath) as! Transects
cell.transectNameLabel.text = transectEntry.transectName
cell.transectNameLabel.textColor = UIColor.blackColor()
cell.transectNameLabel.shadowColor = UIColor.whiteColor()
cell.transectNameLabel.shadowOffset = CGSizeMake(1, 1)
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let countEntry = fetchedResultController.objectAtIndexPath(indexPath) as! Transects
coreDataStack.context.deleteObject(countEntry)
coreDataStack.saveContext()
}
}
func tableView(tableView: UITableView,
heightForRowAtIndexPath indexPath: NSIndexPath)
-> CGFloat {
return 50;
}
func didFinishViewController(viewController: AddTransectViewController, didSave: Bool) {
if didSave {
var error: NSError? = nil
let context = viewController.context
self.coreDataStack.saveContext()
}
dismissViewControllerAnimated(true, completion: {})
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "addTransectSegue" {
let newTransectViewController = segue.destinationViewController as! AddTransectViewController
let transectEntryEntity = NSEntityDescription.entityForName("Transects", inManagedObjectContext: coreDataStack.context)
let newTransectEntry = Transects(entity: transectEntryEntity!, insertIntoManagedObjectContext: coreDataStack.context)
newTransectViewController.transectNewEntry = newTransectEntry
newTransectViewController.context = newTransectEntry.managedObjectContext
newTransectViewController.delegate = self
}
if segue.identifier == "transectTasksSegue" {
let indexPath = tableView.indexPathForSelectedRow()!
let transectSelected = fetchedResultController.objectAtIndexPath(indexPath) as! Transects
let tasksViewController = segue.destinationViewController as! TransectTasksViewController
tasksViewController.coreDataStack = coreDataStack
tasksViewController.selectedTransect = transectSelected
}
}
func controllerDidChangeContent(controller:
NSFetchedResultsController) {
tableView.reloadData()
}
The addition view is:
import UIKit
import CoreData
import Foundation
protocol TransectDelegate {
func didFinishViewController(ViewController:AddTransectViewController, didSave:Bool)
}
class AddTransectViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var transectNameTextField: UITextField!
#IBOutlet weak var latitudeTextField: UITextField!
#IBOutlet weak var longitudeTextField: UITextField!
#IBOutlet weak var altitudeTextField: UITextField!
var transectNewEntry: Transects!
var context: NSManagedObjectContext!
var delegate:TransectDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func updateTransectEntry() {
if let entry = transectNewEntry {
entry.transectName = transectNameTextField.text
entry.latitude = latitudeTextField.text
entry.longitude = longitudeTextField.text
entry.altitude = altitudeTextField.text
}
}
#IBAction func cancelButtonWasTapped(sender: AnyObject) {
delegate?.didFinishViewController(self, didSave: false)
}
#IBAction func saveButtonWasTapped(sender: AnyObject) {
updateTransectEntry()
delegate?.didFinishViewController(self, didSave: true)
}
}
I am missing something, but cannot see what. Ideas would be welcome.
The app hangs up on cell.transectNameLabel.text = transectEntry.transectName
with: Thread 1:EXC_BAD_ACCESS (code=1, address=0x0)
My real confusion is that this works perfectly:
import UIKit
import CoreData
class PlantSpeciesViewController: UIViewController, NSFetchedResultsControllerDelegate, PlantSpeciesDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView:UITableView!
var coreDataStack: CoreDataStack!
lazy var fetchedResultController:
NSFetchedResultsController = self.plantSpeciesFetchedResultsController()
var plantSpecies: PlantSpecies!
var selectedFamily: PlantFamily!
var context: NSManagedObjectContext!
var plantFamilyName: String!
override func viewDidLoad() {
super.viewDidLoad()
tableView.backgroundColor = UIColor.clearColor()
view.backgroundColor = UIColor(patternImage: UIImage (named: "Monitor backdrop.png")!)
self.title = plantFamilyName
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func plantSpeciesFetchedResultsController()
->NSFetchedResultsController {
fetchedResultController =
NSFetchedResultsController(
fetchRequest: plantSpeciesFetchRequest(),
managedObjectContext: coreDataStack.context,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultController.delegate = self
var error: NSError? = nil
if (!fetchedResultController.performFetch(&error)){
println("Error: \(error?.localizedDescription)")
abort()
}
return fetchedResultController
}
func plantSpeciesFetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "PlantSpecies")
fetchRequest.fetchBatchSize = 20
let predicate = NSPredicate(format: "familyName == %#", selectedFamily)
fetchRequest.predicate = predicate
let sortDescriptor = NSSortDescriptor(key: "plantSpecies", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
//var error: NSError?
return fetchRequest
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultController.sections!.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedResultController.sections![section].numberOfObjects
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("plantSpeciesCell", forIndexPath: indexPath) as! PlantSpeciesTableViewCell
cell.backgroundColor = UIColor.clearColor()
configureCell(cell, indexPath: indexPath)
return cell
}
func configureCell(cell: PlantSpeciesTableViewCell, indexPath:NSIndexPath) {
let plantEntry = fetchedResultController.objectAtIndexPath(indexPath) as! PlantSpecies
cell.speciesNameLabel.text = plantEntry.plantSpecies
cell.speciesNameLabel.textColor = UIColor.blackColor()
cell.speciesNameLabel.shadowColor = UIColor.whiteColor()
cell.speciesNameLabel.shadowOffset = CGSizeMake(1, 1)
cell.speciesImageView.image = UIImage (data: plantEntry.plantImage)
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let countEntry = fetchedResultController.objectAtIndexPath(indexPath) as! PlantFamily
coreDataStack.context.deleteObject(countEntry)
coreDataStack.saveContext()
}
}
func tableView(tableView: UITableView,
heightForRowAtIndexPath indexPath: NSIndexPath)
-> CGFloat {
return 90;
}
func didFinishViewController(viewController: AddPlantSpeciesViewController, didSave: Bool) {
if didSave {
var error: NSError? = nil
let context = viewController.context
self.coreDataStack.saveContext()
}
dismissViewControllerAnimated(true, completion: {})
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "addSpeciesSegue" {
let newPlantViewController = segue.destinationViewController as! AddPlantSpeciesViewController
let plantEntryEntity = NSEntityDescription.entityForName("PlantSpecies", inManagedObjectContext: coreDataStack.context)
let newSpeciesEntry = PlantSpecies(entity: plantEntryEntity!, insertIntoManagedObjectContext: coreDataStack.context)
newPlantViewController.selectedFamily = selectedFamily
newPlantViewController.plantNameEntry = newSpeciesEntry
newPlantViewController.context = newSpeciesEntry.managedObjectContext
newPlantViewController.delegate = self
}
}
func controllerDidChangeContent(controller:
NSFetchedResultsController) {
tableView.reloadData()
}
}
coupled with:
import UIKit
import CoreData
import Foundation
protocol PlantSpeciesDelegate {
func didFinishViewController(ViewController:AddPlantSpeciesViewController, didSave:Bool)
}
class AddPlantSpeciesViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#IBOutlet weak var plantNameTextField: UITextField!
#IBOutlet weak var plantImageView: UIImageView!
#IBOutlet weak var imageSwitch: UISwitch!
#IBOutlet weak var imageFromFileButton: UIButton!
#IBOutlet weak var imageFromCameraButton: UIButton!
let imagePicker = UIImagePickerController()
var plantNameEntry: PlantSpecies!
var selectedFamily: PlantFamily!
var passedPlantFamily: String!
var newPlantName: String!
var newImageData: NSData!
var context: NSManagedObjectContext!
var delegate:PlantSpeciesDelegate?
override func viewDidLoad() {
super.viewDidLoad()
plantImageView.image = UIImage(named: "placeholder image.jpg")
imagePicker.delegate = self
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func imageSourceSwitch(sender: AnyObject) {
if imageSwitch.on == true
{
self.imageFromFileButton.enabled = true
self.imageFromCameraButton.enabled = false
}
else
{
self.imageFromCameraButton.enabled = true
self.imageFromFileButton.enabled = false
}
}
#IBAction func imageFromFile(sender: AnyObject) {
imagePicker.sourceType = .PhotoLibrary
presentViewController(imagePicker, animated: true, completion: nil)
}
#IBAction func imageFromCamera(sender: AnyObject) {
imagePicker.sourceType = .Camera
presentViewController(imagePicker, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
self.plantImageView.image = image
dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func getPlantName() {
newPlantName = plantNameTextField.text
plantNameTextField.resignFirstResponder()
}
func updateSpeciesEntry() {
if let entry = plantNameEntry {
entry.plantSpecies = newPlantName
entry.plantImage = UIImageJPEGRepresentation(plantImageView.image, 1.0)
entry.familyName = selectedFamily
}
}
#IBAction func cancelButtonWasTapped(sender: AnyObject) {
delegate?.didFinishViewController(self, didSave: false)
}
#IBAction func saveButtonWasTapped(sender: AnyObject) {
updateSpeciesEntry()
delegate?.didFinishViewController(self, didSave: true)
}
}
So, what is the difference?
This happen to me too, fixed it with:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
instead of just tableView.reloadData()
, cause it seems that it was being called from wrong thread.
The reason for the crash is most likely that the table cannot load the data, for example a value does not exist and is being force unwrapped. The crash only happens, therefore, when you try to collect the data. Check through all the values to be sure.
I'd like to expand on GJZ answer & what brought me here. I have UITableViewCells that have a textfield. I attempted to get values the user entered from the field.
let companyNameCell: TextEntryCell = self.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as! TextEntryCell
what I realized is that when I dynamically hide and showed rows by changing the cell heights using the tableview.reloadrows method, the app would crash if it attempted to read a cell the user could not see on their screen. that is because the cells I think were deallocated.
So, this is what's happening: I have two controllers; MainViewController and DetailsTableViewController. On DetailsTableViewController I want to write a name for random recipe, then I pick an image from photo library. After that, I want to send those data to a cell in MainViewController. The code this:
import UIKit
import CoreData
class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, sendDetailsToMVCDelegate {
#IBOutlet weak var mainImage: UIImageView!
#IBOutlet weak var tableView: UITableView!
var namesListArray: [String] = []
var imagesListArray: [UIImage] = []
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
}
func sendDetailsToMVC (name: String, image: UIImage) {
namesListArray.append(name)
imagesListArray.append(image)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return namesListArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var row = indexPath.row
var name: String = namesListArray[row] as String
var image: UIImage = imagesListArray[row] as UIImage
var cell = self.tableView.dequeueReusableCellWithIdentifier("customCell") as! CustomCellTableViewCell
cell.customLabel!.text = (name as String)
cell.customImage.image = (image as UIImage)
return cell
}
}
and
import UIKit
import CoreData
protocol sendDetailsToMVCDelegate {
func sendDetailsToMVC(name: String, image: UIImage)
}
class DetailsViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITableViewDataSource, UITableViewDelegate, sendNameToDetailsViewControllerDelegate {
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var recipeImage: UIImageView!
#IBOutlet weak var tableView: UITableView!
var ingredientsList = [""]
var delegateDetails: sendDetailsToMVCDelegate?
override func viewDidLoad() {
super.viewDidLoad()
let howToDoButton = UIBarButtonItem(title:"How To Do", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("showHowToDoScreen"))
navigationItem.rightBarButtonItem = howToDoButton
let newIngredientButton = UIBarButtonItem(title:"New Ingredient", style: UIBarButtonItemStyle.Plain, target:self, action: Selector("showNewIngredientScreen"))
navigationItem.leftBarButtonItem = newIngredientButton
//Picker imagem pelo toque
let tapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "chooseImage:")
tapGestureRecognizer.numberOfTapsRequired = 1
recipeImage.addGestureRecognizer(tapGestureRecognizer)
recipeImage.userInteractionEnabled = true
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ingredientsList.count
}
func addNameToDetailsViewController(nameToDetail: NSString) {
ingredientsList.append(nameToDetail as String)
if tableView == nil {
return
}
tableView!.reloadData()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let row = indexPath.row
let ingredientName = ingredientsList[row]
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: nil)
cell.textLabel!.text = ingredientName
return cell
}
//Picka a imagem pelo toque, acessando a PhotoLibrary
func chooseImage(recognizer: UITapGestureRecognizer) {
let imagePicker: UIImagePickerController = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
self.presentViewController(imagePicker, animated: true, completion: nil)
}
//Ao selecionar a imagem, coloca-a na tela tocada
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject:AnyObject]) {
let pickedImage: UIImage = (info as NSDictionary).objectForKey(UIImagePickerControllerOriginalImage) as! UIImage
// small picture
let smallPicture = scaleImageWith(pickedImage, newSize: CGSizeMake(75,75))
var sizeOfImageView:CGRect = recipeImage.frame
sizeOfImageView.size = smallPicture.size
recipeImage.frame = sizeOfImageView
recipeImage.image = smallPicture
picker.dismissViewControllerAnimated(true, completion: nil)
}
func imagePickerControllerDidCancel(picker: UIImagePickerController) {
picker.dismissViewControllerAnimated(true, completion: nil)
}
func scaleImageWith(image:UIImage, newSize: CGSize) -> UIImage {
UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
image.drawInRect(CGRectMake(0,0, newSize.width, newSize.height))
let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
#IBAction func showNewIngredientScreen() {
let newIngredient = AddIngredientViewController(delegate: self)
if let navigation = navigationController {
navigation.pushViewController(newIngredient, animated: true)
}
}
#IBAction func showHowToDoScreen() {
let howToDo = HowToDoViewController(nibName: "HowToDoViewController", bundle: nil)
if let navigation = navigationController {
navigation.pushViewController(howToDo, animated: true)
}
}
#IBAction func addButton(sender: AnyObject) {
if nameTextField == nil || recipeImage == nil { return }
if recipeImage == nil { return }
let nameLabel = nameTextField.text
let imageView = recipeImage.image
if delegateDetails == nil { return }
delegateDetails?.sendDetailsToMVC(nameLabel, image: imageView!)
println("button pressed, name \(nameLabel) and image \(imageView) added.")
if let navigation = self.navigationController {
navigation.popViewControllerAnimated(true)
}
}
My question is: am I passing the information correctly? Is the correct way for storing images creating images arrays like I did? When I press the addButton, nothing happens, but it has no crashes. I'm in this issue for quite some time already and I would be very thankful if someone pointed to me what's wrong.
By the way, I'm new to Swift, so forgive me for any n00b mistakes.
Thanks!
Should be the same as in your other question. In the addButton function you are only setting the delegate if not niland not if you should. So delete if delegateDetails == nil { return } from the DetailViewControler - addButton and it should work.