Connecting to Superclass Properties

What's the best way to connect a Storyboard element to a superclass property? Do you create an outlet in the subclass and then set that property to the superclass property?


Superclass Property

var storyTableView: UITableView!


Subclass and setting the superclass property equal to the sublass outlet.

class FLOViewController
{
@IBOutlet var floViewControllerTableView: UITableView!

    override func viewDidLoad()
    {
        super.viewDidLoad()
        self.storyTableView = self.floViewControllerTableView
     }
}

This appears to work but is seems wrong to me. It seems as if copying the object is not the best idea. Any help would be appreciated.


Take care,


Jon

I still do ObjC. I wouldn't expect Interface Builder behavior to be any different just because you are using Swift. Just make the superclasses property an IBOutlet?


If that is considered 'wrong' in Swift...then yikes.

The question is how/why/when the superclass expects its "storyTableView" property to be set. What you're doing might be the right approach, but there's certainly a code smell about it, which can't be resolved without knowing more about the superclass.


It's possible that the correct answer is to mark the superclass property as "@IBOutlet", which should allow you to connect the property in the storyboard.


Note that with Swift 2, the right approach might be to turn the superclass into a protocol with an extension that provides default behavior.

Thanks for the reply. The superclass is a CoreDataFetchedResultsController. It's used to handle all of the heavy lifting with Core Data. The reason for connecting the table to the superclass is so that I can access all of the methods and update the table.


I tried the IBOutlet option and could not find it when I tried to connect the tableview. It did not show up as an option when I control click dragged to the viewController that subclassed the superclass. Am I missing something here?


I'd be happy to look into the protocol option with Swift 2. Do you have any resource that would explain that process? It doens't look like the documentaion is up to date yet.


Take care,


Jon

CoreDataFetchedResultsController is not a Cocoa class, so I don't know anything about it. Are you saying it's a class you wrote, or that you have source code for?


Note that in the code you posted, FLOViewController isn't actually a subclass of it, so it's not clear whether your source code is correct.


Regarding protocols, I suggest you want the "What's New in Swift" video from this year's WWDC. There's a section in there about protocol extensions, IIRC. You can also read about them in the new version of the Swift iBook, or the Swift documentation on the developer site.

Here is the full @interface and @implementation of the CoreDataFetchedResultsController Class. I am using a bridging header file for since it's in Objective-C. I've also included the .swift file that is a subclass. I am sorry for the impcomlete code in the code above.\


Thanks for the recommendation on the new Swift stuff. I'll be sure to take a look.


Take care,


Jon


.h

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
@interface CoreDataFetchedResultsViewController : UIViewController <NSFetchedResultsControllerDelegate, UITableViewDataSource, UITableViewDelegate>
/
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, strong) UITableView *storyTableView;
@property (nonatomic, strong) UIRefreshControl *refreshControl;
/
@property (nonatomic) BOOL suspendAutomaticTrackingOfChangesInManagedObjectContext;
@property BOOL debug;
/
- (void) performFetch;
@end

.m

@interface CoreDataFetchedResultsViewController ()
@property (nonatomic) BOOL beganUpdates;
@end
@implementation CoreDataFetchedResultsViewController
@synthesize fetchedResultsController = _fetchedResultsController;
@synthesize suspendAutomaticTrackingOfChangesInManagedObjectContext = _suspendAutomaticTrackingOfChangesInManagedObjectContext;
@synthesize refreshControl;
@synthesize storyTableView;
/
/
#pragma mark - Fetching
- (void)performFetch
{
    if (self.fetchedResultsController)
    {
        if (self.fetchedResultsController.fetchRequest.predicate)
        {
            if (self.debug) NSLog(@"[%@ %@] fetching %@ with predicate: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName, self.fetchedResultsController.fetchRequest.predicate);
        } else
        {
            if (self.debug) NSLog(@"[%@ %@] fetching all %@ (i.e., no predicate)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName);
        }
     
        NSError *error;
     
        BOOL success = [self.fetchedResultsController performFetch:&error];
     
        if (!success) NSLog(@"[%@ %@] performFetch: failed", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
        if (error) NSLog(@"[%@ %@] %@ (%@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), [error localizedDescription], [error localizedFailureReason]);
    } else
    {
        if (self.debug) NSLog(@"[%@ %@] no NSFetchedResultsController (yet?)", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
    }

    [self.storyTableView reloadData];
}
/
- (void)setFetchedResultsController:(NSFetchedResultsController *)newfrc
{
    NSFetchedResultsController *oldfrc = _fetchedResultsController;

    if (newfrc != oldfrc)
    {
        _fetchedResultsController = newfrc;
        newfrc.delegate = self;
        if ((!self.title || [self.title isEqualToString:oldfrc.fetchRequest.entity.name]) && (!self.navigationController || !self.navigationItem.title))
        {
            self.title = newfrc.fetchRequest.entity.name;
        }
     
        if (newfrc)
        {
            if (self.debug) NSLog(@"[%@ %@] %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), oldfrc ? @"updated" : @"set");
            [self performFetch];
        } else
        {
            if (self.debug) NSLog(@"[%@ %@] reset to nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
            [self.storyTableView reloadData];
        }
    }
}
/
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [[self.fetchedResultsController sections] count];

    /
    /
}
/
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
    /NSInteger rows = 0;
    if ([[self.fetchedResultsController sections] count] > 0)
    {
        id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
        rows = [sectionInfo numberOfObjects];
    }
    return rows;*/

    /
    /
}
/
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
  return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}
/
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
  return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
/
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
    return [self.fetchedResultsController sectionIndexTitles];
}
/
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
        [self.storyTableView beginUpdates];
        self.beganUpdates = YES;
    }
}
/
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
        switch(type)
        {
            case NSFetchedResultsChangeInsert:
                [self.storyTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                break;
             
            case NSFetchedResultsChangeDelete:
                [self.storyTableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationNone];
                break;
            case NSFetchedResultsChangeMove:
                break;
            case NSFetchedResultsChangeUpdate:
                break;
        }
    }
}
/
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
        switch(type)
        {
            case NSFetchedResultsChangeInsert:
                [self.storyTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;
             
            case NSFetchedResultsChangeDelete:
                [self.storyTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
                break;
             
            case NSFetchedResultsChangeUpdate:
                [self.storyTableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;
             
            case NSFetchedResultsChangeMove:
                [self.storyTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                [self.storyTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;
        }
    }
}
/
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{

    if (self.beganUpdates) [self.storyTableView endUpdates];
}
/
- (void)endSuspensionOfUpdatesDueToContextChanges
{
    _suspendAutomaticTrackingOfChangesInManagedObjectContext = NO;
}
/
- (void)setSuspendAutomaticTrackingOfChangesInManagedObjectContext:(BOOL)suspend
{
    if (suspend) {
        _suspendAutomaticTrackingOfChangesInManagedObjectContext = YES;
    } else {
        [self performSelector:@selector(endSuspensionOfUpdatesDueToContextChanges) withObject:0 afterDelay:0];
    }
}
/
@end


.swift

import UIKit
import CoreData
class FLOViewController: CoreDataFetchedResultsViewController, FLODataHandlerDelegate, UITableViewDataSource, UITableViewDelegate
{
    /
    let kFLOCyclingURL = "http:/
    let kTriathleteURL = "http:/
    let kVeloNewsURL = "http:/
    let kCyclingNewsURL = "http:/
    let kRoadBikeActionURL = "http:/
    let kIronmanURL = "http:/
  
    var dataHandler : FLODataHandler?
    var articleView : ArticleViewController?
  
    let appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
    lazy var articleContext: NSManagedObjectContext = self.appDelegate.managedObjectContext!
   
    /
    @IBOutlet weak var backgroundImage: UIImageView!
    @IBOutlet var floViewControllerTableView: UITableView!
   
    / 
    override func viewDidLoad()
    {
        super.viewDidLoad()
       
        /
        self.storyTableView = self.floViewControllerTableView
        self.refreshControl = UIRefreshControl()
        self.refreshControl!.addTarget(self, action: "refreshInvoked:state:", forControlEvents: UIControlEvents.ValueChanged)
        /
        println(self.storyTableView)
        println(self.refreshControl)
        self.storyTableView.addSubview(self.refreshControl!)
        /
    }
    / 
    override func viewWillAppear(animated: Bool)
    {
        /
        self.setUpFetchedResultsController()
    }
    / 
    override func didReceiveMemoryWarning()
    {
        super.didReceiveMemoryWarning()
    }
   
    / 
    func setUpFetchedResultsController()
    {
        var fetchRequest = NSFetchRequest(entityName: "Article")
        var predicate = NSPredicate(format: "feed == 'FLO Cycling'")
        fetchRequest.predicate = predicate
        var sortDescriptors = NSSortDescriptor(key: "pubDate", ascending: false)
        fetchRequest.sortDescriptors = [sortDescriptors]
        self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.articleContext, sectionNameKeyPath: nil, cacheName: nil)
    }
   
    / 
    @IBAction func back(sender: AnyObject)
    {
        /
        self.navigationController?.popViewControllerAnimated(true)
    }
   
    / 
    @IBAction func markAllAsRead(sender: AnyObject)
    {
        /
        let alert = UIAlertController(title: nil, message: nil, preferredStyle: UIAlertControllerStyle.ActionSheet)
       
        /
        let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil)
        alert.addAction(cancelAction)
       
        /
        let markAllAsReadAction = UIAlertAction(title: "Mark All As Read", style: UIAlertActionStyle.Default, handler: {(alert: UIAlertAction!)
            in
            let fetchRequest = NSFetchRequest(entityName: "Article")
            let predicate = NSPredicate(format: "feed == 'FLO Cycling'")
            fetchRequest.predicate = predicate
            var fetchError : NSError?
            let fetchedObjects = self.articleContext.executeFetchRequest(fetchRequest, error: &fetchError) as! [Article]
           
            for element in fetchedObjects
            {
                if (element.theNewArticle.intValue == 1)
                {
                    element.theNewArticle = NSNumber(int: 0)
                }
               
                var error : NSError?
                if (self.articleContext.save(&error))
                {
                    println(error!.localizedDescription)
                }
            }
        })
        alert.addAction(markAllAsReadAction)
       
        /
        self.presentViewController(alert, animated: true, completion: nil)
    }
  
    / 
    func refreshInvoked(sender: AnyObject, state: UIControlState)
        /
    {
        let URLTempDictionary = ["FLO Cycling" : kFLOCyclingURL, "Triathlete" : kTriathleteURL, "Velo News" : kVeloNewsURL, "Cycling News" : kCyclingNewsURL, "Road Bike Action" : kRoadBikeActionURL, "Ironman" : kIronmanURL]
       
        /
        self.dataHandler = FLODataHandler(myDictionary: URLTempDictionary, myDelegate: self)
       
        /
        self.dataHandler!.startConnectionHandler()
    }
   
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
    {
        let cell = tableView.dequeueReusableCellWithIdentifier("FloArticleCell", forIndexPath: indexPath) as! FloArticleCell
       
        /
        let selectedCellColor = UIView()
        selectedCellColor.backgroundColor = UIColor(red: 0.0, green: 174.0/255.0, blue: 239.0/255, alpha: 0.8)
        cell.selectedBackgroundView = selectedCellColor
       
        /
        /
        cell.preservesSuperviewLayoutMargins = false
        cell.layoutMargins = UIEdgeInsetsZero
        cell.separatorInset = UIEdgeInsetsZero
       
        /
        var tempArticle = self.fetchedResultsController!.objectAtIndexPath(indexPath) as? Article
       
        if tempArticle!.theNewArticle.intValue == 1
        {
            /
            cell.articleTwoLineTitleLabel.text = ""
            cell.articleTwoLineDateLabel.text = ""
           
            /
            /
            var myLabel = UILabel(frame: CGRectMake(0,0,282,350))
            myLabel.font = UIFont(name: "Avenir-Heavy", size: 14.0)
            myLabel.numberOfLines = 0
            myLabel.lineBreakMode = NSLineBreakByWordWrapping
            myLabel.text = tempArticle!.title
            var labelSize = myLabel.text!.boundingRectWithSize(
            */
           
            /
            cell.articleTwoLineTitleLabel.text = tempArticle!.title
           
            /
            var articleDateFormat = NSDateFormatter()
            articleDateFormat.dateFormat = "EEE, MMM d yyyy, hh:mm aa"
            var articleDateString = articleDateFormat.stringFromDate(tempArticle!.pubDate)
            cell.articleTwoLineDateLabel.text = articleDateString
            cell.articleDot.hidden = false
           
            /var dateFormatter = NSDateFormatter()
            dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss ZZ"
            /
            var tempDate = dateFormatter.dateFromString(tempString!)*/
        }
        else
        {
            /
            cell.articleTwoLineTitleLabel.text = ""
            cell.articleTwoLineDateLabel.text = ""
           
            /
            cell.articleTwoLineTitleLabel.text = tempArticle!.title
           
            /
            var articleDateFormat = NSDateFormatter()
            articleDateFormat.dateFormat = "EEE, MMM d yyyy, hh:mm aa"
            var articleDateString = articleDateFormat.stringFromDate(tempArticle!.pubDate)
            cell.articleTwoLineDateLabel.text = articleDateString
            cell.articleDot.hidden = true
        }
       
        /let tempArticle = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Article
       
        if tempArticle.theNewArticle.intValue == 1
        {
           
        }*/
       
        return cell
    }
   
    / 
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
    {
        /
        let selectedArticle = self.fetchedResultsController!.objectAtIndexPath(indexPath) as? Article
       
        /
        selectedArticle!.theNewArticle = NSNumber(int: 0)
       
        var error : NSError?
        if (!self.articleContext.save(&error))
        {
            println(error)
        }
        /
        self.articleView = self.storyboard!.instantiateViewControllerWithIdentifier("ArticleViewController") as? ArticleViewController
        self.articleView!.currentArticle = selectedArticle!
        self.articleView!.articleContext = self.articleContext
        println("This is coming from didSelectRowAtIndexPath \(self.articleView!.currentArticle!.link)")
        / 
        self.navigationController!.pushViewController(self.articleView!, animated: true)
       
        / 
        tableView.deselectRowAtIndexPath(indexPath, animated: true)
    }
   
    /
   
    func floDataHanlderDidFinishLoading(dataHandler : FLODataHandler) -> ()
    {
        /
        self.refreshControl!.endRefreshing()
    }
   
    /
   
    func floDataHandlerDidFailWithError(dataHandler : FLODataHandler, error : NSError) -> ()
    {
       
    }
}
Accepted Answer

I still do ObjC. I wouldn't expect Interface Builder behavior to be any different just because you are using Swift. Just make the superclasses property an IBOutlet?


If that is considered 'wrong' in Swift...then yikes.

It was my mistake tryong to connect the property. This is acceptable in Swift and you are correct that this is the proper approach. I change the superclass storyTableView property to an IBOutlet and connected it to mysubclasses tableView. Thanks for all of the help and back and forth on this one.


Take care,


Jon

I don't know whether to expect difficulties with this mixed Swift/Obj-C/IBOutlet scenario. In principle it ought to work.


Looking at the superclass implementation, though, it seems to me that your original approach is OK. The superclass merely needs its storyTableView property set, and doing it in the subclass viewDidLoad is the earliest that you can safely do so. The only thing I'd consider adding to the superclass is a validation check (in, say viewWillAppear, since its own viewDidLoad would be too early) that storyTableView is non-nil.


There's another possible solution, where the subclass actually overrides the storyTableView property. You'd mark the subclass property IBOutlet, which may force you to mark the superclass property IBOutlet too, not sure what the compiler rules are here, and connect the subclass property in IB. The superclass property would then never be used by subclass instances, with the net gain being the removal of the "extra" subclass property, and the removal of that one line in subclass viewDidLoad. (If you used this approach, you can use @dynamic storyTableView in the superclass, instead of @synthesize storyTableView, to prevent it even having an implementation in the superclass.) But these are refinements. If your original solution is working, I don't think you're doing anything scandalous by leaving it alone.

Connecting to Superclass Properties
 
 
Q