When I rotate the app twice after selecting a few items, it crashes. I have overridden the sendEvent method and that's where the debugger stops. When I try to print the event type, it shows me something weird (I think it's a memory location that doesn't exist):
(lldb) print event.type
(UIEventType) $R10 = <invalid> (0xff)
Somehow I think this is related to how I handle the rotation. I have a master-detail style application, that uses a different type of navigation for pad-landscape, pad-portrait and phone. I have created a class named NavigationFlowController which handles all navigational events and sets up the views accordingly. On rotation, it breaks up the view trees and recomposes them with the correct navigation
func changeViewHierarchyForDevideAndOrientation(newOrientation:UIInterfaceOrientation? = nil){
print("MA - Calling master layout method")
UIApplication.myDelegate().window?.frame = UIScreen.mainScreen().bounds
let idiom = UIDevice.currentDevice().userInterfaceIdiom
var orientation:UIInterfaceOrientation!
if let no = newOrientation{
orientation = no
}else{
orientation = UIApplication.sharedApplication().statusBarOrientation
}
print("MA - Breaking up view tree...")
breakupFormerViewTree([sidebarViewController, listViewController, detailViewController, loginViewController])
print("MA - Start init navbackbone")
initNavBackboneControllers()
guard let _ = UIApplication.myDelegate().currentUser else {
if idiom == UIUserInterfaceIdiom.Phone{
currentState = AppState.PHONE
}else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsLandscape(orientation){
currentState = AppState.PAD_LANDSCAPE
}else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsPortrait(orientation){
currentState = AppState.PAD_PORTRAIT
}
print("MA - Current user is nil - resetting")
mainViewController.addChildViewController(loginViewController)
return
}
if idiom == UIUserInterfaceIdiom.Phone{
currentState = AppState.PHONE
leftNavigationController?.viewControllers = [listViewController]
slideViewController?.rearViewController = sidebarViewController
slideViewController?.frontViewController = leftNavigationController
slideViewController?.rearViewRevealWidth = 267;
mainViewController.addChildViewController(slideViewController!)
}else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsLandscape(orientation){
currentState = AppState.PAD_LANDSCAPE
leftNavigationController!.viewControllers = [sidebarViewController, listViewController]
rightNavigationController!.viewControllers = [detailViewController]
detailViewController.navigationItem.leftBarButtonItems = []
detailViewController.initLayout()
print("MA - Init split view controller with VCs")
splitViewController!.viewControllers = [leftNavigationController!, rightNavigationController!]
mainViewController.addChildViewController(splitViewController!)
}else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsPortrait(orientation){
currentState = AppState.PAD_PORTRAIT
leftNavigationController!.pushViewController(sidebarViewController, animated: false)
leftNavigationController!.pushViewController(listViewController, animated: false)
rightNavigationController!.pushViewController(detailViewController, animated: false)
rightNavigationController?.setNavigationBarHidden(false, animated: false)
slideViewController!.rearViewController = leftNavigationController
slideViewController!.frontViewController = rightNavigationController
detailViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "< Documenten", style: UIBarButtonItemStyle.Bordered, target: slideViewController, action: "revealToggle:")
detailViewController.initLayout()
slideViewController!.rearViewRevealWidth = 350;
mainViewController.addChildViewController(slideViewController!)
}
}
func breakupFormerViewTree(vcs:[UIViewController?]){
for vc in vcs{
if let vcUnwrapped = vc, _ = vcUnwrapped.parentViewController {
vcUnwrapped.removeFromParentViewController()
vcUnwrapped.view.removeFromSuperview()
}
}
}
func initNavBackboneControllers(){
leftNavigationController = UINavigationController()
leftNavigationController?.navigationBar.barTintColor = UIColor(red: 0.25, green: 0.25, blue: 0.25, alpha: 1.0)
leftNavigationController?.navigationBar.tintColor = UIColor.whiteColor()
leftNavigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
leftNavigationController?.navigationBar.translucent = false
rightNavigationController = UINavigationController()
rightNavigationController?.navigationBar.barTintColor = UIColor(red: 0.25, green: 0.25, blue: 0.25, alpha: 1.0)
rightNavigationController?.navigationBar.tintColor = UIColor.whiteColor()
rightNavigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
rightNavigationController?.navigationBar.translucent = false
slideViewController = SWRevealViewController()
slideViewController?.rearViewRevealOverdraw = 0;
slideViewController?.bounceBackOnOverdraw = false;
slideViewController?.stableDragOnOverdraw = true;
slideViewController?.delegate = self
if UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Pad{
splitViewController = UISplitViewController()
}
}
EDIT (in response to Justin's questions):
1) I've experienced the crash on all iOS8 iPad simulators.
2) From a fresh start, if I select like 6-7 items and then I rotate twice, it crashes. But I can also select an item, rotate a few times, select some more and keep rotating and at some point it will crash.
3) When an item is selected, the following code is executed:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let document = getInfoForSection(indexPath.section).documents[indexPath.item]
if document.canOpen{
openDocument(document)
DataManager.sharedInstance.getDocument(document.uri, after: {
(document:Document?) -> () in
if let documentUnwrapped = document{
let detailVC = NavigationFlowController.sharedInstance.detailViewController;
if detailVC.document?.uri == documentUnwrapped.uri{
NavigationFlowController.sharedInstance.detailViewController.documentUpdated(documentUnwrapped)
}
}
})
}
}
And then in the detail view controller:
func initLayout(){
if viewForCard == nil{
// views not yet initialized, happens when initLayout if called from the document setter before this view has been loaded
// just return, the layouting will be done on viewDidLoad with the correct document instead
return
}
self.navigationItem.rightBarButtonItems = []
if document == nil{
// Removed code that handles no document selected
...
return
}
heightForCard.constant = NavigationFlowController.sharedInstance.currentState == AppState.PHONE ? CARD_HEIGHT_PHONE : CARD_HEIGHT_TABLET
viewForCard.hidden = false
removeAllSubviews(viewForCard)
removeAllSubviews(viewForDetails)
viewForDetails.translatesAutoresizingMaskIntoConstraints = false
self.metaVC?.document = document
//self.documentVC?.document = document
self.navigationItem.rightBarButtonItems = []
downloadDocumentIfNeeded()
if NavigationFlowController.sharedInstance.currentState == AppState.PAD_LANDSCAPE || NavigationFlowController.sharedInstance.currentState == AppState.PAD_PORTRAIT{
self.viewForDetails.backgroundColor = document?.senderStyling?.color
addChildViewController(self.metaVC!)
addChildViewController(self.documentVC!)
let metaView = self.metaVC!.view
let documentView:UIView = self.documentVC!.view
viewForDetails.addSubview(metaView)
viewForDetails.addSubview(documentView)
// whole lot of layouting code removed
...
let doubleTap = UITapGestureRecognizer(target: self, action: "toggleZoom")
documentVC!.view.addGestureRecognizer(doubleTap)
}else{
// Phone version code removed
...
}
}
EDIT2:
func downloadDocumentIfNeeded(){
var tmpPath:NSURL?
if let url = document?.contentUrl{
let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
if let docName = self.document?.name,
safeName = disallowedCharacters?.stringByReplacingMatchesInString(docName, options: [], range: NSMakeRange(0, docName.characters.count), withTemplate: "-"){
tmpPath = directoryURL.URLByAppendingPathComponent("\(safeName)_\(DetailViewController.dateFormatter.stringFromDate(self.document!.creationDate!)).pdf")
}
if let urlString = tmpPath?.path{
if NSFileManager.defaultManager().fileExistsAtPath(urlString) {
// File is there, load it
loadDocumentInWebview(tmpPath!)
}else{
// Download file
let destination: (NSURL, NSHTTPURLResponse) -> (NSURL) = {
(temporaryURL, response) in
if let path = tmpPath{
return path
}
return temporaryURL
}
download(.GET, URLString: url, destination: destination).response {
(request, response, data, error) in
if error != nil && error?.code != 516{
ToastView.showToastInParentView(self.view, withText: "An error has occurred while loading the document", withDuaration: 10)
}else if let pathUnwrapped = tmpPath {
self.loadDocumentInWebview(pathUnwrapped)
}
}
}
}
}
}
func loadDocumentInWebview(path:NSURL){
if self.navigationItem.rightBarButtonItems == nil{
self.navigationItem.rightBarButtonItems = []
}
self.documentVC?.finalPath = path
let shareItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Action, target: self, action: "share")
shareItem.tag = SHARE_ITEM_TAG
addNavItem(shareItem)
}
func addNavItem(navItem:UIBarButtonItem){
var addIt = true
for item in self.navigationItem.rightBarButtonItems!{
if item.tag == navItem.tag{
addIt = false
}
}
if addIt{
self.navigationItem.rightBarButtonItems?.append(navItem)
self.navigationItem.rightBarButtonItems!.sortInPlace({ $0.tag > $1.tag })
}
}
EDIT3: I've overridden the sendEvent method to track whether or not a user is touching the app or not, but even if I take out this code, it still crashes, and the debugger then breaks on UIApplicationMain.
override func sendEvent(event: UIEvent)
{
super.sendEvent(event)
if event.type == UIEventType.Touches{
if let touches = event.allTouches(){
for item in touches{
if let touch = item as? UITouch{
if touch.phase == UITouchPhase.Began{
touchCounter++
}else if touch.phase == UITouchPhase.Ended || touch.phase == UITouchPhase.Cancelled{
touchCounter--
}
if touchCounter == 0{
receiver?.notTouching()
}
}
}
}
}
}
Tough one, a bit more insight in the events upto this bug might be helpful.
Does it happen on every device (if not, which devices gives you troubles)
It happens after "vigorously selecting" items. Did your device change orientation before that. Does it also happen before you once rotate?
What do you do in code when you "select an item".
Other then that, I'd start to get the flow of removing your child ViewControllers in breakupFormerViewTree() right. Based on the Apple Docs you want to tell the child it's being removed, before removing the view and then finally removing the child from the Parent ViewController
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html
Here it actually says you want to call willMoveToParentViewController(nil) before doing the removing. It doesn't say what happens if you don't, but I can imagine the OS doing some lifecycle management there, preventing it from sending corrupt events at a later point.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/instm/UIViewController/willMoveToParentViewController:
EDIT (After extra was code posted)
I don't see anything else in your code that might cause it to crash. It does look like a memory-error as you stated, but no idea where it's coming from. Try turning on Zombie objects and Guard Malloc (Scheme > Run > Diagnostics) and maybe you can get a bit more info on what's causing it.
Other then that, I'd just comment out loads of your implementation, swap Subclasses with empty ViewControllers until it doesn't happen again. You should be able to pinpoint what part of your implementation is involved in creating this event. Once you do that, well, pinpoint more and evaluate every single line of code in that implementation.
Related
I am currently working as a 5 month junior ios developer.
The project I'm working on is an application that shows the prices of 70 cryptocurrencies realtime with websocket connection.
we used websocket connection, UItableview, UITableViewDiffableDataSource, NSDiffableDataSourceSnapshot while developing the application.
But right now there are problems such as slowdown scrolling or not stop scroling and UI locking while scrolling in the tableview because too much data is processed at the same time.
after i check cpu performance with timer profiler I came to the conclusion that updateDataSource and updateUI functions exhausting the main thread.
func updateDataSource(model: [PairModel]) {
var snapshot = DiffableDataSourceSnapshot()
let diff = model.difference(from: snapshot.itemIdentifiers)
let currentIdentifiers = snapshot.itemIdentifiers
guard let newIdentifiers = currentIdentifiers.applying(diff) else {
return
}
snapshot.appendSections([.first])
snapshot.deleteItems(currentIdentifiers)
snapshot.appendItems(newIdentifiers)
dataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
}
func updateUI(data: SocketData) {
guard let newData = data.data else { return }
guard let current = data.data?.price else { return }
guard let closed = data.data?.lastDayClosePrice else { return }
let dailyChange = ((current - closed)/closed)*100
DispatchQueue.main.async { [self] in
if model.filter({ $0.symbol == newData.pairSymbol }).first != nil {
let index = model.enumerated().first(where: { $0.element.symbol == newData.pairSymbol})
guard let location = index?.offset else { return }
model[location].price = current
model[location].dailyPercent = dailyChange
if calculateLastSignalTime(alertDate: model[location].alertDate) > 0 {
//Do Nothing
} else {
model[location].alertDate = ""
model[location].alertType = ""
}
if let text = allSymbolsView.searchTextField.text {
if text != "" {
filteredModel = model.filter({ $0.name.contains(text) || $0.symbol.contains(text) })
updateDataSource(model: filteredModel)
} else {
filteredModel = model
updateDataSource(model: filteredModel)
}
}
}
delegate?.pricesChange(data: self.model)
}
}
Regards.
ALL of your code is running on the main thread. You have to wrap your entire updateUI function inside a DispatchQueue.global(qos:), and then wrap your dataSource.apply(snapshot) line inside a DispatchQueue.main.async. the dataSource.apply(snapshot) line is the only UI work you're doing in all that code you posted.
Hey guys I need your help please, when I reload a tableview and call the scroll to row function for the bottom row (array.length - 1) as index path, it scrolls to it, then it scrolls one row up. I have made a cool chat part to my app, and it is simple, I have a function that grabs messages, and for some reason on my iPhone simulator, it does this weird scrolling back up motion. Basically I was hoping one of you guys could help me find out why it is doing that. It is some simple code.
main function:
func grabMessages () {
if let uid = Auth.auth().currentUser?.uid {
if let theirId = theirUid {
let ref = Database.database().reference()
ref.child("users").child(uid).child("chats").child(theirId).child("messages").observe(.value, with: {(snapshot) in
var reloadi = false
if let values = snapshot.value as? [String : AnyObject] {
for (_, one) in values {
if let whoSent = one["sender"] as? String, let messagl = one["message"] as? String, let timerl = one["timeStamp"] as? Int, let keyer = one["key"] as? String {
let newMess = Message()
print("googd")
newMess.key = keyer
newMess.timeStamp = timerl
newMess.messager = messagl
newMess.sender = whoSent
if self.messages.contains( where: { $0.key == newMess.key } ) {
} else {
self.messages.append(newMess)
if self.messages.count != 0 {
self.messages.sort { $1.timeStamp > $0.timeStamp }
reloadi = true
}
if newMess.sender == theirId {
let update = ["unseen" : "iViewed"]
ref.child("users").child(theirId).child("chats").child(uid).updateChildValues(update)
}
}
}
if reloadi == true {
reloadi = false
DispatchQueue.main.async {
self.tablerView.reloadData()
let indexPat = IndexPath(row: 0, section: self.messages.count - 1)
self.tablerView.isHidden = false
self.tablerView.scrollToRow(at: indexPat, at: .bottom, animated: false)
}
}
}
}, withCancel: nil)
}
}
}
I will say in my viewdidload I set the frame of the tableview, which is just a cgrect and same size across all platform, fixed width and height. And on my own iPhone it runs and works fine, only on simulator iPhones did it do this.
Here is a screen shot
https://imgur.com/a/ZAhbJ
So if you can see, it shows other section below it, but it is scrolled to the one above it. So it originally scrolls to it, then scrolls back up one section.
some other stuff
numb of sections = messages.count
number of rows in section = 1
I'm working on Swift as iOS Developer and I'm having some trouble lately adding subviews in asynchronous way, I'm using dispatch_async(dispatch_get_main_queue() but still it doesn't work. The subviews are not adding one by one and in real time but I have to wait till the call is done then the UI is updated. My goal in this is to add subviews one by one and in real time while the for loop is still going. Below is a part of my code. If somebody can help me I would really appreciate it.
for i in 0 ..< self.allChannels.count {
let param = Params().getEpgParams(String(self.allChannels[i].channelNumber))
uNetwork().httpRequestNoLoading(self.utilityClass.currentServer , parameters: param, method: uNetwork.METHOD.POST, parent: self) { (epgResponse, extra_information) -> () in
if(extra_information != uNetwork.EXTRA_INFORMATION.SUCCESS){
self.presentViewController(self.utilityClass.alert("Error", message: "An Unexpected Error Happened"), animated: true, completion: nil)
} else if(epgResponse == nil){
self.presentViewController(self.utilityClass.alert("Error", message: "Couldn't get any response from the server"), animated: true, completion: nil)
} else {
do {
var titleArray = [String]()
var start = [String]()
var end = [String]()
var progressArray = [Float]()
let jsonObject = try NSJSONSerialization.JSONObjectWithData(epgResponse, options: []) as! NSDictionary
if let response_object = jsonObject["response_object"] as? [[String: AnyObject]] {
for menu in response_object{
titleArray.append(menu["title"] as! String)
start.append(menu["programstart"] as! String)
end.append(menu["programend"] as! String)
progressArray.append(menu["progress"] as! Float)
}
if(titleArray.count > 0){
currentTitle = titleArray[0]
currentStart = start[0][start[0].startIndex.advancedBy(12)..<start[0].startIndex.advancedBy(12+4)]
currentEnd = end[0][end[0].startIndex.advancedBy(12)..<end[0].startIndex.advancedBy(12+4)]
progressValue = Float(progressArray[0] / 100)
} else {
currentTitle = "Programet e \(self.allChannels[i].title)"
currentStart = "00:00"
currentEnd = "00:00"
progressValue = 0.5
}
dispatch_async(dispatch_get_main_queue(), {
indiSub[i].removeFromSuperview()
self.contentView = UIView(frame: CGRectMake(0, ((self.scrollView.frame.height / 4) * CGFloat(i)), self.epgView.frame.width, self.scrollView.frame.height / 4))
self.contentView.layer.masksToBounds = true
self.scrollView.addSubview(self.contentView)
let button = UIButton(frame: CGRectMake(0, ((self.scrollView.frame.height / 4) * CGFloat(i)), self.epgView.frame.width, self.scrollView.frame.height / 4))
button.tag = Int(self.allChannels[i].channelNumber)
button.addTarget(self, action: #selector(LiveTvTest.playFromEpg(_:)), forControlEvents: .TouchUpInside)
self.scrollView.addSubview(button)
let currentTime = UILabel(frame: CGRectMake((self.scrollView.frame.height / 4) + 25, 0, 60, self.contentView.frame.height / 3))
if currentStart.characters.count == 4{
currentTime.text = currentStart + " |"
} else if (currentStart.characters.count == 5){
currentTime.text = currentStart + " |"
}
currentTime.font = UIFont(name: "Helvetica Neue", size: 15)
currentTime.adjustsFontSizeToFitWidth = true
currentTime.textColor = UIColor(red:0.54, green:0.13, blue:0.13, alpha:1.0)
self.contentView.addSubview(currentTime)
let nextTime = UILabel(frame: CGRectMake((self.scrollView.frame.height / 4) + 25, currentTime.frame.height, 60, self.contentView.frame.height / 3))
if nextStart.characters.count == 4{
nextTime.text = nextStart + " |"
} else if (nextStart.characters.count == 5) {
nextTime.text = nextStart + " |"
}
nextTime.font = UIFont(name: "Helvetica Neue", size: 15)
nextTime.adjustsFontSizeToFitWidth = true
nextTime.textColor = UIColor(red:0.53, green:0.53, blue:0.53, alpha:1.0)
self.contentView.addSubview(nextTime)
})
} else {
print("something went wrong with response object")
}
} catch {
self.presentViewController(self.utilityClass.alert("Error", message: "Something went wrong, please try again later"), animated: true, completion: nil)
}
}
}
}
If you can ensure that your closure (that you hand in to httpRequestNoLoading) is running in an arbitrary thread, you could use dispatch_sync your UI-updates into the main thread; this will wait until the work item as executed. But if the close already runs in the main thread, this will surely deadlock.
Btw. you should also dispatch the presentViewController calls into the main thread.
I'd guess the problem is that you're setting up a set of http calls. Those calls get executed in parallel and return via the callback. then you're making several view updates very closely together, thus, even if the code is serial, the updates happen in quick succession.
That said, the other thing to keep in mind is that osx/ios batch their ui updates. so it could be that you're putting quite a bit of work on the mainQueue at once, and aren't getting a chance to see the work happen. Why not try adding in some random delays using dispatch_after to see if that makes things pop in at different times?
I am modifying my paid for app to a free with IAP.
Some group types are free and some will need to be paid for.
On the tableView controller, there is a padlock UIImageView that displays each "locked" item if it is a part of a locked group.
When someone buys that group, or all, that padlock is meant to disappear. This padlock is not a part of the code that prevents the user from seeing the details as I have that code somewhere else and it works fine.
Initially, "Padlock" displays accurately. However, If I scroll up and down on the tableView, the padlocks will randomly disappear.
How to prevent this?
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
//
// We need to fetch our reusable cell.
//
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: Resource.SpeciesCell)!
//
// Get all of the components of the cell.
//
let specieImage: UIImageView = cell.viewWithTag(Resource.SpeciesImageTag) as! UIImageView
let specieName: UILabel = cell.viewWithTag(Resource.SpeciesNameTag) as! UILabel
let specieGenus: UILabel = cell.viewWithTag(Resource.SpeciesGenusTag) as! UILabel
let specieFamily: UILabel = cell.viewWithTag(Resource.SpeciesFamilyTag) as! UILabel
// Set our name, family and genus labels from our data.
//
specieName.text = self.species[(indexPath as NSIndexPath).row].specie
specieFamily.text = ""
specieGenus.text = self.species[(indexPath as NSIndexPath).row].speciesSubgroup
// **** Adding Padlock Begins HERE ****
let padLock: UIImageView = cell.viewWithTag(Resource.SpeciesCategoryLabelTag) as! UIImageView
padLock.image = UIImage(named: "PadlockIcon")
padLock.alpha = 0.7
let speciesGroupFreeArray: Array<String>
let speciesGroupVertebratesArray: Array<String>
let speciesGroupInvertebratesArray: Array<String>
speciesGroupFreeArray = ["eels", "Mammals", "Annelids", "Bivalvians", "Cephalopods", "Cool oddities", "Crustacean", "Echinoderms", "Hydrozoans", "Isopods"]
speciesGroupVertebratesArray = ["Fish", "Sharks", "Rays", "Reptilia", "Syngnathiformes"]
speciesGroupInvertebratesArray = ["Corals", "Gastropods"]
let fishesPurchased = UserDefaults.standard.bool (forKey: "ReefLife5Fishes")
let sharksPurchased = UserDefaults.standard.bool (forKey: "ReefLife6Sharks")
let nudisPurchased = UserDefaults.standard.bool (forKey: "ReefLife7Nudis")
let turtlesPurchased = UserDefaults.standard.bool (forKey: "ReefLife8Turtles")
let seahorsesPurchased = UserDefaults.standard.bool (forKey: "ReefLife9Seahorses")
let coralsPurchased = UserDefaults.standard.bool (forKey: "ReefLife4Corals")
let vertebratesPurchased = UserDefaults.standard.bool (forKey: "ReefLife3Vertebrates")
let invertebratesPurchased = UserDefaults.standard.bool (forKey: "ReefLife2Invertebrates")
let fullPurchased = UserDefaults.standard.bool (forKey: "ReefLife1Full")
let categoryName = self.species[(indexPath as NSIndexPath).row].group
if fullPurchased == true {
padLock.isHidden = true
} else if speciesGroupVertebratesArray.contains(categoryName) {
if vertebratesPurchased == true {
padLock.isHidden = true
} else {
if categoryName == "Fish" {
if fishesPurchased == true{
padLock.isHidden = true
} else{
}
} else if (categoryName == "Sharks" || categoryName == "Rays" ) {
if sharksPurchased == true{
padLock.isHidden = true
} else{
}
} else if categoryName == "Syngnathiformes" {
if seahorsesPurchased == true{
padLock.isHidden = true
} else{
}
} else if categoryName == "Reptilia" {
if turtlesPurchased == true{
padLock.isHidden = true
} else{
}
}
}
} else if speciesGroupInvertebratesArray.contains(categoryName) {
if invertebratesPurchased == true {
padLock.isHidden = true
} else {
if categoryName == "Corals" {
if coralsPurchased == true{
padLock.isHidden = true
} else{
}
} else if categoryName == "Gastropods" {
if nudisPurchased == true{
padLock.isHidden = true
} else{
}
}
}
}
if speciesGroupFreeArray.contains(categoryName) {
padLock.isHidden = true
}
// **** Adding Padlock Ends HERE ****
//
// We now need to set our photo. First, we need to fetch the photo based on our specie
// id.
//
let photos = AppDelegate.getRLDatabase().getSpeciePhotos(self.species[(indexPath as NSIndexPath).row].id)
//
// Set the image for our UIImageView.
//
if photos.isEmpty != true
{
specieImage.clipsToBounds = true
specieImage.contentMode = .scaleAspectFill
specieImage.image = UIImage(named: photos[0].name)
// specieImage.image = UIImage(named: photos[0].name)?.resizeTo(specieImage.bounds)
}
else
{
specieImage.image = UIImage(named: Resource.UnknownImage)?.resizeTo(specieImage.bounds)
}
//
// Return our new cell.
//
return cell
}
Please try replace this
if speciesGroupFreeArray.contains(categoryName) {
padLock.isHidden = true
}
to
padLock.isHidden = speciesGroupFreeArray.contains(categoryName)
Your code only ever sets padlock.isHidden = true. If you get a recycled cell where the padlock is already hidden, there's nothing to unhide it.
You need to explicitly set the padlock isHidden state in all cases.
Given your somewhat contorted code for setting the isHidden property, the simplest way to fix your current code would be to set padlock.isHidden = false At the beginning of your cellForRowAt() method. Then, if none of the cases set isHidden to true, it will always be unbidden, even in a case where you are reconfiguring a recycled cell where the padlock was hidden.
EDIT:
This a key take-away for managing table views and collection views in iOS. Always assume that a cell comes to you with all views set to a non-default state. Your code always needs to explicitly set every view to a specific state.
Think of a table view cell as a paper patient info form at a doctor's office where the office recycles the forms, and doesn't erase the info left by the previous patient. You always have to erase the info left by the previous patient. If you don't have an allergy to peanuts, it's not enough to simply not check that box. You have to erase the previous check marks and other info, or the previous patient's info will get mixed up with yours.
That's what's happening with your padlock.isHidden state. You're assuming it starts out in the default state, but it may not.
I have an entity called Settings with an attribute called backgroundColor of type Int, if it is 1 then the view controller will have a background of white if 0 then a background of dark grey.
But I am getting the following error when trying to open the view controller;
fatal error: Array Index out of range
For the following line in my function
if settingsArray.count == 1 {
setting = settingsArray[1]
} else if settingsArray.count <= 0 {
println("No settings in array")
}
View Controller
var settingsArray: [Settings]!
var setting: Settings!
var backgroundSetting: Bool = true
override func viewWillAppear(animated: Bool) {
backgroundSettings()
}
override func viewDidLoad() {
super.viewDidLoad()
backgroundSettings()
}
// Function to fetch settings and change background
func backgroundSettings() {
var error: NSError?
let request = NSFetchRequest(entityName: "Settings")
self.settingsArray = moc?.executeFetchRequest(request, error: &error) as! [Settings]
if settingsArray.count == 1 {
setting = settingsArray[1]
} else if settingsArray.count <= 0 {
println("No settings in array")
}
if setting != nil {
if setting.backgroundColor == 1 {
backgroundSetting = true
} else if setting.backgroundColor == 0{
backgroundSetting = false
}
}
if backgroundSetting == true {
self.view.backgroundColor = UIColor.whiteColor()
} else if backgroundSetting == false {
self.view.backgroundColor = UIColor.darkGrayColor()
}
}
//Button to change the color and settings
#IBAction func backgroundColor(sender: AnyObject) {
if setting != nil {
if setting.backgroundColor == 1 {
setting.backgroundColor = 0
} else {
setting.backgroundColor = 1
}
var error: NSError?
moc?.save(&error)
} else {
println("No settings available")
var settings = NSEntityDescription.insertNewObjectForEntityForName("Settings", inManagedObjectContext: moc!) as! Settings
settings.backgroundColor = 1
var error: NSError?
moc?.save(&error)
}
backgroundSettings()
}
Any ideas where I may be going wrong ?
In Swift (as in Objective C and many other languages), the indexes of arrays start at 0. (See this wikipedia article for a list on this.)
This means when you check if settingsArray.count == 1, there will be only (exactly) one item in your list. Since indexes start at 0, this item will be at index 0, hence the Error.
So either you check if settingsArray.count == 2 and leave setting = settingsArray[1], or you change to setting = settingsArray[0].
ETA:
I have been having another think about this. I have left my old answer below so that the previous comments make sense.
In if let thisSetting = settingsArray?[0] … if settingsArray is nil then the right side is potentially effectively nil[0]. Therefore I believe that this may eliminate the crash:
// ...
if !settingsArray.isEmpty {
if let thisSetting = settingsArray?[0] {
setting = thisSetting
}
}
settingsArray[0] being nil would I think then be a separate issue. Which I think would relate to the lifecycle.
Previous answer follows:
I believe that the problem may be being caused by you calling func backgroundSettings() from viewDidLoad() - i.e. too early in the View Controller lifecycle - and before the values have been initialized.