Custom iOS "load more" button in row list - ios

I'm using a UItableview with a scrolling list. Now I have the default "load more" button that loads 10 objects at a time. It destroys the interface. Any ideas on how to customize it? A tried a few things but we no luck. Thanks

Try implementing this method that Parse provides for customizing the "Load more" cell:
// Override to customize the look of the cell that allows the user to load the next page of objects.
// The default implementation is a UITableViewCellStyleDefault cell with simple labels.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForNextPageAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"NextPage";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.text = #"Load more...";
return cell;
}

Well there is not much you can do with Parse controller in terms of customization. Also the animation of that controller seems to be a bit clunky. I also got a bunch of memory errors while using the component with latest IOs 9.
For that same reason I've replaced the existing Parse controller with the following. Gave me much more control over the animations and design than the standard PARSE controller.
If you want to reverse the order of the tableview to look like a messenger app please have a look here
add global variables
var objects = [PFObject(className: "Posts")]
var lockScrollSensor = false
let objectsPerPage = 10
add to viewDidLoad
//blank screen
let blankView = UIView(frame: CGRectMake(0, 0, self.tableView.frame.size.width, self.tableView.frame.size.height - 60))
blankView.tag = 101
blankView.backgroundColor = customColorYellow()
self.view.addSubview(blankView)
self.tableView.backgroundColor = customColorYellow()
// load tableview
tableViewFirstLoad()
Detect the position of tableview scroll to either call refresh or next page
override func scrollViewDidScroll(scrollView: UIScrollView) {
let scrollViewHeight = scrollView.frame.size.height;
let scrollContentSizeHeight = scrollView.contentSize.height;
let scrollOffset = scrollView.contentOffset.y;
// refresh - touch top
if (scrollOffset < -60)
{
if lockScrollSensor == false {
// stop method from non stop calling
lockScrollSensor = true
// loader
let spinningActivity = MBProgressHUD.showHUDAddedTo(self.parentViewController?.view, animated: true)
spinningActivity.color = UIColor.clearColor()
spinningActivity.activityIndicatorColor = customColorRed()
spinningActivity.yOffset = -Float((self.parentViewController?.view.frame.height)!/2) + 95
// call method to load more
self.tableViewRefresh({ (done) -> Void in
if done == true
{
// stop loader and free method
spinningActivity.hide(true, afterDelay: 0)
self.lockScrollSensor = false
// animate tableview
self.tableView.contentOffset.y = -60
UIView.animateWithDuration(1.5, delay: 1, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: UIViewAnimationOptions.CurveEaseIn, animations: { () -> Void in
self.tableView.contentOffset.y = 0
}, completion: nil)
}
})
}
}
// load more - touch down
else if (scrollOffset + scrollViewHeight > scrollContentSizeHeight)
{
if lockScrollSensor == false {
// stop method from non stop calling
lockScrollSensor = true
// start loader
let spinningActivity = MBProgressHUD.showHUDAddedTo(self.parentViewController?.view, animated: true)
spinningActivity.color = customColorYellow()
spinningActivity.yOffset = (Float(tableView.frame.size.height)/2)-25
// call method to load more
self.tableViewLoadMore({ (done) -> Void in
if done == true {
self.lockScrollSensor = false
spinningActivity.hide(true)
}
})
}
}
}
Refresh or next page methods that will load your PFObject array.
// first load
func tableViewFirstLoad(result: (done: Bool) -> Void){
var query = PFQuery(className: "Posts")
query = segueDataChecker()
// limit objects
query.limit = objectsPerPage
// get objects
query.findObjectsInBackgroundWithBlock { (posts: [AnyObject]?, error: NSError?) -> Void in
if error != nil
{
result(done: true)
let alertView = createDefaultAlertError(message: "\(error!.localizedDescription.capitalizedString)", actions: [UIAlertAction(title: "Ok", style: UIAlertActionStyle.Cancel, handler: nil)])
self.presentViewController(alertView, animated: true, completion: nil)
}
else
{
// clean array
self.objects.removeAll()
// populate array
for eachPost in posts!
{
if self.objects.count < self.objectsPerPage
{
self.objects.append(eachPost as! PFObject)
}
}
// reload and send back it has finished
self.tableView.reloadData()
result(done: true)
self.tableView.backgroundColor = UIColor(patternImage: UIImage(named: "pattern_clear_white.pdf")!)
// remove uiview
UIView.animateWithDuration(0.5, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
if let viewWithTag = self.view.viewWithTag(101)
{
viewWithTag.alpha = 0
}
}, completion: nil)
}
}
}
// refresh existing content
func tableViewRefresh(result: (done: Bool) -> Void){
var query = PFQuery(className: "Posts")
query = segueDataChecker()
// limit objects
query.limit = self.objects.count
// save number of objects already loaded
let actualNumberOfPosts = self.objects.count
// get objects
query.findObjectsInBackgroundWithBlock { (posts: [AnyObject]?, error: NSError?) -> Void in
if error != nil
{
result(done: true)
let alertView = createDefaultAlertError(message: "\(error!.localizedDescription.capitalizedString)", actions: [UIAlertAction(title: "Ok", style: UIAlertActionStyle.Cancel, handler: nil)])
self.presentViewController(alertView, animated: true, completion: nil)
}
else
{
// clean array
self.objects.removeAll()
// populate array with same updated objects
for eachPost in posts!
{
if self.objects.count < actualNumberOfPosts
{
self.objects.append(eachPost as! PFObject)
}
}
// reload and send back it has finished
self.tableView.reloadData()
result(done: true)
}
}
}
// load next results
func tableViewLoadMore(result: (done: Bool) -> Void){
var query = PFQuery(className: "Posts")
query = segueDataChecker()
// skip objects already loaded
query.skip = self.objects.count; // skip the first 20 results
// limit results to next page amount
query.limit = objectsPerPage
// save number of objects already loaded
let actualNumberOfPosts = self.objects.count
// get objects
query.findObjectsInBackgroundWithBlock { (posts: [AnyObject]?, error: NSError?) -> Void in
if error != nil
{
result(done: true)
let alertView = createDefaultAlertError(message: "\(error!.localizedDescription.capitalizedString)", actions: [UIAlertAction(title: "Ok", style: UIAlertActionStyle.Cancel, handler: nil)])
self.presentViewController(alertView, animated: true, completion: nil)
}
else
{
// if there anything new
if posts?.count != 0
{
// populate array with new objects
for eachPost in posts!
{
if (self.objects.count < self.objectsPerPage + actualNumberOfPosts)
{
self.objects.append(eachPost as! PFObject)
}
}
// reload and send back it has finished
self.tableView.reloadData()
result(done: true)
// slide tablview one result down
let newIndexPath = NSIndexPath(forRow: actualNumberOfPosts, inSection: 0)
self.tableView.scrollToRowAtIndexPath(newIndexPath , atScrollPosition: UITableViewScrollPosition.None, animated: true)
}
// if there is nothing new
else
{
result(done: true)
}
}
}
}

Related

How can we reusable a Function With Different ViewController?

#objc func contextDidSave(_ notification: Notification) {
DispatchQueue.main.async { [weak self] in
guard let me = self else { return }
let fileCount = CoreDataService.instance.getUploadedFilesCount(jobId: nil)
if me.totalFiles == 0 || (fileCount > me.totalFiles) {
me.totalFiles = fileCount
}
// For increase the progress lebel we need the range between 0.1(min) to 1.0(max)
// So converting all values to float so that we can get decimal value within the range
let progress = (Float(me.totalFiles) - Float(fileCount)) / Float(me.totalFiles)
me.progressBarView.setProgress(progress, animated: true)
if fileCount == 0 {
me.progressView.isHidden = true
} else {
me.uploadFiles.text = "\("Uploading: ".localized() + (fileCount.description) + " Files left".localized())"
me.progressView.isHidden = false
}
}
}
ProgressView is UIView
UploadFile is UILabel
This function is reusable in Two Different ViewController

Tableview sliding up after scroll to row

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

Like button doesn't work every time

I'm trying to make a social media app and one of the features is the like button, unfortunately when you click everything works fine but all of the time it doesn't update the amount of likes correctly and the like button correctly. I believe it's something to do with the refreshing it but I need it to be refreshed so that it can update the amount of likes. Does anyone know what's going on...
func like(sender: AnyObject) {
var buttonPosition: CGPoint = sender.convertPoint(CGPointZero, toView: self.table)
var indexPath: NSIndexPath = self.table.indexPathForRowAtPoint(buttonPosition)!
if sender.currentTitle == "Like" {
sender.setTitle("Unlike", forState: .Normal)
var addLikeQuery = PFQuery(className: "Post")
addLikeQuery.whereKey("message", equalTo: self.messages[indexPath.row])
addLikeQuery.findObjectsInBackgroundWithBlock { (aPosts, error) -> Void in
if let aPosts = aPosts {
for aPost in aPosts {
aPost.addUniqueObject(PFUser.currentUser()!.objectId!, forKey: "likers")
self.likeDisplayText = ((aPost["likers"] as! [String]).count - 1).description + " Like"
self.table.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
aPost.saveInBackgroundWithBlock({ (success, error) -> Void in
if error != nil {
self.likeDisplayText = "Couldn't Like Picture!"
}
})
}
}
}
} else {
sender.setTitle("Like", forState: .Normal)
var removeLikeQuery = PFQuery(className: "Post")
removeLikeQuery.whereKey("message", equalTo: self.messages[indexPath.row])
removeLikeQuery.findObjectsInBackgroundWithBlock { (rPosts, error) -> Void in
if let rPosts = rPosts {
for rPost in rPosts {
rPost.removeObject(PFUser.currentUser()!.objectId!, forKey: "likers")
self.likeDisplayText = ((rPost["likers"] as! [String]).count - 1).description + " Like"
self.table.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
rPost.saveInBackgroundWithBlock({ (success, error) -> Void in
if error != nil {
self.likeDisplayText = "Couldn't Like Picture!"
}
})
}
}
}
}
}
I'm not exactly sure what your question is but i may have some insight. If you are trying to edit some elemts in your UI through a function that does a lot of processing or is in another thread i suggest making it Asynchronus and anywhere in that function where you are editing UI make that code run on the main thread. Here's what i mean.
func like(sender: AnyObject) {
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
// do background stuff
dispatch_async(dispatch_get_main_queue()) {
// when you want to edit the text of the button
// or change something in your UI
// do it in here
self.likeDisplayText = "Couldn't Like Picture!"
}
}
}

App crash on sendEvent method

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.

IBOutlet nil after refreshing data

I'm using XCode 6.3.1 and I'm facing a weird problem that I can't figure it out.
I have a View Controller on my Storyboard opened with a modal segue. When the ViewController is opened it loads some data from my backend (Parse) it looks first on the cache and shows cached data (if exists) while the updated data from server is retrieved on the background. The process is the next:
Get cached data
If cached data exists then update interface
Request server data (In background)
When data arrives update interface
Everything works fine until step 4. When I try to refresh my interface suddenly half of my #IBOutlets are nil and of course app crashes.
what am I missing??
Here's the code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//eventId is set when the ViewController is instantiated
if eventId != nil {
loadEvent(eventId)
}
}
func loadEvent(id: String) {
var query = Event.query()
query?.cachePolicy = Util.getCachePolicy() //First look in cache, the request network data
query?.getObjectInBackgroundWithId(id, block: { (event: PFObject?, error: NSError?) -> Void in
if error == nil {
var updatedEvent = event as! Event
self.event = updatedEvent
self.updateLayout()
//When self.updateLayout() is called with cached data
//all my IBOutlets are fine but when it's called the second time,
//with data from server half of the IBOutlets are nil
}
})
}
func updateLayout() {
if event != nil {
eventTitle.text = event.name
var paletteColor : UIColor!
var location = event.location
var locationName = location["name"] as! String
eventLocation.text = NSString(format: NSLocalizedString("event_subtitle", comment: ""), event.formattedTimes(), locationName) as String
eventDescription.text = event.abstract
if event.paletteColor != 0 {
paletteColor = Util.colorFromInt(event.paletteColor)
eventHeader.backgroundColor = paletteColor
speakersBlockTitle.textColor = paletteColor
mapButton.tintColor = paletteColor
}
if event.hasPhoto() {
self.eventPhoto.file = event.eventPhoto
self.eventPhoto.loadInBackground({ (image:UIImage?, error:NSError?) -> Void in
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.eventPhoto.alpha = 1.0
})
})
} else {
self.eventPhoto.removeFromSuperview()
if paletteColor == nil {
paletteColor = eventHeader.backgroundColor
}
actionBar.backgroundColor = paletteColor
}
if event.speaker.isDataAvailable() {
var speaker = event.speaker
speakerName.text = speaker["name"] as? String
speakerInfo.text = speaker["speakerInfo"] as? String
speaker["speakerPhoto"]?.getDataInBackgroundWithBlock({ (imageData:NSData?, error:NSError?) -> Void in
if error == nil {
self.speakerPhoto.image = UIImage(data:imageData!)
self.speakerPhoto.layer.cornerRadius = self.speakerPhoto.frame.size.width/2
self.speakerPhoto.clipsToBounds = true
}
})
} else {
speakerBlock.removeFromSuperview()
}
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.eventHeader.alpha = 1.0
self.eventDescription.alpha = 1.0
self.speakerBlock.alpha = 1.0
self.mapButton.alpha = 1.0
})
}
}
This are all the nil IBOutlets:
Since the code in my comment above doesn't display correctly, here's that comment again:
Something like this:
#IBOutlet weak var myOutlet: UIButton! {
didSet {
if myOutlet == nil {
// put a breakpoint on the next line
println("myOutlet set to nil")
}
}
}

Resources