Array Index Out Of Range - Error when optional unbinding - ios

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.

Related

UITableView scrolling performance problem

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.

How to fix conditional binding have optional type not 'Bool'?

how can I solve this problem?
Ive been getting the same error in 10 different places, I have been running tests on it and can't seem to figure this
thanks in advance for any help that you guys provide it really means a lot to me
Initializer for conditional binding must have Optional type, not 'Bool'
extension HomeController: FiltersViewControllerDelegate{
func query(withCategory jewelry: Bool, shoe: Bool, hat: Bool, apearel: Bool, gear: Bool) -> Query {
if jewelry == false && shoe == false && hat == false && apearel == false && gear == false {
stackViewHeightConstraint.constant = 0
activeFiltersStackView.isHidden = true
} else {
stackViewHeightConstraint.constant = 44
activeFiltersStackView.isHidden = false
}
var filtered = baseQuery
// Sort and Filter data
if let jewelry = jewelry, !jewelry.isEmpty { //Error
filtered = filtered.whereField("category", isEqualTo: jewelry)
}
//......more Filters....\\
if let gear = gear, !gear.isEmpty { //Error
filtered = filtered.whereField("category", isEqualTo: gear)
}
return filtered
}
func controller(_ controller: FilterViewController,
didSelectCategory jewelry: Bool,
shoe: Bool,
hat: Bool,
apearel: Bool,
gear: Bool) {
if jewelry == false && shoe == false && hat == false && apearel == false && gear == false {
stackViewHeightConstraint.constant = 0
activeFiltersStackView.isHidden = true
} else {
stackViewHeightConstraint.constant = 44
activeFiltersStackView.isHidden = false
}
let filtered = query(withCategory: jewelry, shoe: shoe, hat: hat, apearel: apearel, gear: gear)
if let jewelry = jewelry, ! jewelry.isEmpty { //Error
jewelryFilterLbl.text = "Jewelry"
jewelryFilterLbl.isHidden = false
} else {
jewelryFilterLbl.isHidden = true
}
//......more Filters....\\
if let gear = gear, !gear.isEmpty { //Error
gearFilterLbl.text = "gear"
gearFilterLbl.isHidden = false
} else {
gearFilterLbl.isHidden = true
}
query = filtered
}
}
Remove .isEmpty check it's not a property of a Bool
if jewelry { //Error
filtered = filtered.whereField("category", isEqualTo: jewelry)
}
//......more Filters....\\
if gear { //Error
filtered = filtered.whereField("category", isEqualTo: gear)
}
You're using if let binding on variables that are not optionals.
For example, your jewel variable is a Bool, not a Bool?. Using optional binding doesn’t make any sense.
if let jewelry = jewelry, ! jewelry.isEmpty { // Jewelry isn't an Optional!!!
jewelryFilterLbl.text = "Jewelry"
jewelryFilterLbl.isHidden = false
} else {
jewelryFilterLbl.isHidden = true
}
Plus, as other users have stated, Bool variables don't have .isEmpty method. Revise your logic and your code, it doesn't work at all.

UISegmentedControl and .selectedSegmentIndex wrong values?

I have a simple Segment in my code with 3 elements. For testing purposes I also do have a variable that increments based on which of the segments I press (3). The value of that variable is printed in a UITextView. This is the code:
import UIKit
class ViewController: UIViewController
{
#IBOutlet weak var segment: UISegmentedControl!
#IBOutlet weak var prwtoView: UIView!
#IBOutlet weak var prwtoText: UITextField!
var i : Int = 0
override func viewWillAppear(animated: Bool)
{
prwtoText.backgroundColor = UIColor.purpleColor()
prwtoText.textColor = UIColor.whiteColor()
segment.setTitle("Zero", forSegmentAtIndex: 0)
segment.addTarget(self, action: "action", forControlEvents: .ValueChanged)
segment.insertSegmentWithTitle("random", atIndex: 2, animated: false)
}
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
i = 0
}
if argumentForSegment == 1
{
i += 2
}
else
{
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
}
While I know that it starts with value -1 i don't want my app to do anything if not pressed. The thing is that even when I press the first segment (0) and it is supposed to make i = 0 it doesn't do that, although if I print argumentForSegment in my terminal it does show the 0 as value. Concluding, every time I press the zero segment (0), my i value won't become 0. Perhaps I am using the wrong method from UISegmentedControl?
edit: Got it fixed by changing the following code:
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
i = 0
}
if argumentForSegment == 1
{
i += 2
}
else
{
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
to:
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
i = 0
}
if argumentForSegment == 1
{
i += 2
}
else if argumentForSegment == 2 // <==== here
{
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
Could someone explain why it used the priority of else although the value was zero when printing argumentForSegment? In other words why when I had an else alone for the value of argumentForSegment == 0 it chose the else instead of the first statement?
Could someone explain why it used the priority of else although the
value was zero when printing argumentForSegment? In other words why
when I had an else alone for the value of argumentForSegment == 0 it
chose the else instead of the first statement?
When you have a situation where the code is not behaving as you expect, it is helpful to step through it in the debugger, or add some diagnostic print statements.
For example:
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
print("In first block")
i = 0
}
if argumentForSegment == 1
{
print("In second block")
i += 2
}
else
{
print("In third block")
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
If you do this, you will notice that when argumentForSegment is 0, the output will be:
In first block
In third block
So, the problem is not that it is choosing the third block over the first. The problem is that it is doing both. You want it to stop after it has detected that argumentForSegment is 0, so add an else to the second conditional statement so that it only does that when the first conditional statement failed:
func action()
{
let argumentForSegment = segment.selectedSegmentIndex
if argumentForSegment == 0
{
i = 0
}
else if argumentForSegment == 1 // added "else" here
{
i += 2
}
else
{
i += Int(arc4random_uniform(100))
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
To improve on Vacawama's answer, you can format this much easier by using a switch statement:
func action() {
let argumentForSegment = segment.selectedSegmentIndex
switch argumentForSegment {
case 0:
i = 0
case 1:
i += 1
case 2:
i += Int(arc4random_uniform(100))
default:
break
}
print(argumentForSegment)
prwtoText.text = "\(i)"
}
it's much more clean for this type of thing.
(thanks, vacawama)

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