Swift 3 - superclass method is executing instead of its override counterpart - objective-c

How can I override the ObjC methods, runThisMethod and controller:didChangeObject:, from the ViewControllerA class so that the app executes the counterparts from the Swift subclass, ViewControllerC, instead? In the code below, [ViewControllerA runThisMethod(someParameter)] will execute when the app detects a change in fetchedResultsController (via [ViewControllerA controller:didChangeObject:]. Can somebody help me see what I'm missing?
Main.swift
let vc = ViewControllerC.init()
self.myNavigationController?.pushViewController(vc, animated: true)
ViewControllerA.h
#interface ViewControllerA : UIViewController <NSFetchedResultsControllerDelegate>
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
- (void)setUpFetchedResultsController;
- (void)runThisMethod:(SomeParameterClass *)someParameter;
#end
ViewControllerA.m
#implementation ViewControllerA
- (void)setUpFetchedResultsController
{
// build fetch "request" here
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:[[MYCoreDataManager sharedCoreDataManager] managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)someParameter atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
[self runThisMethod:someParameter]
}
- (void)runThisMethod:(SomeParameterClass *)someParameter
{
NSLog(#"This method will execute")
}
#end
ViewControllerB.h
#interface ViewControllerB : ViewControllerA
// declare a bunch of properties
#end
ViewControllerB.m
#implementation ViewControllerB
- (void)viewWillAppear:(BOOL)animated {
[self setUpFetchedResultsController];
}
#end
ViewControllerC.swift
class ViewControllerC: ViewControllerB {
override func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange someParameter: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
// this function won't run
self.runThisMethod(someParameter)
}
override func runThisMethod(someParameter){
// this function won't run
}
}

When I set up a sample project with the code as you've mentioned (and set up a bridging header file which imports in ViewControllerB.h) the overridden runThisMethod on ViewControllerC executes correctly. This is with Xcode 8.2.1. So everything appears to work as it should with the set up you mention.
Could you perhaps upload a sample project showing the behaviour that you mention so that we can take a look? My suspicion is that something is not set up correctly somewhere ...
One thing you might not be doing (since you've only provided part of the code) is overriding NSFetchedResultsController's didChangeObject delegate method in ViewControllerC.

Turns out the problem was in the predicate of my fetchedResultsController property. The predicate had a parameter that will causing fetchResultsController to always be nil when performing the fetch. Thus, the delegate method, controller:didChangeObject: was not being triggered.

Related

Why is NSCollectionView not loading in Objective-C?

I am learning about NSCollectionView compositional layout on macOS (12.4, Xcode 13.4). In the Swift version of this app everything works as expected, yet when converting it to Objective-C, while I get everything to compile, the app loads as blank.
I checked the outlets in the nib file (dataSource and delegate from Collection View to File's Owner), checked the creation of a reusable item (I build it through a custom nib). The error I get in the console when the app launches is as follows:
2022-07-21 15:26:25.817911+0200 Cocoa Pr L68 ObjC[14203:1414415] *** Assertion failure in -[_NSCollectionViewCore _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:], UICollectionView.m:6488
2022-07-21 15:26:25.818378+0200 Cocoa Pr L68 ObjC[14203:1414415] could not dequeue an item of kind: NSCollectionElementKindItem with identifier _NS:8 - must register a nib or a class for the identifier, or name a nib or class to match the identifier
Browsing the docs, I learned that NSCollectionView doesn't have a dequeueReusable kind of method like Table Views in iOS have so the first error is not clear to me. The second is even stranger because I've actually register a class for the identifier:
#import "CollectionViewController.h"
#import "CollectionViewItem.h"
#interface CollectionViewController ()
#property (weak) IBOutlet NSCollectionView *collectionView;
#end
#implementation CollectionViewController
#synthesize collectionView;
- (void)viewDidLoad {
[super viewDidLoad];
CollectionViewItem *collectionViewItem = [[CollectionViewItem alloc] init];
[collectionView registerClass:[NSCollectionViewItem class] forItemWithIdentifier:collectionViewItem.identifier];
[collectionView setCollectionViewLayout:self.listLayout];
}
The listLayout method is defined afterwards in the same file. All NSCollectionViewDataSource methods are properly defined as in the Swift version, for reference:
- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView *)collectionView {
return 3;
}
- (NSInteger)collectionView:(NSCollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 20;
}
- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
NSCollectionViewItem *item = [collectionView makeItemWithIdentifier:collectionView.identifier forIndexPath:indexPath];
[item.textField setStringValue:[NSString stringWithFormat:#"%ld, %ld", indexPath.section, indexPath.item]];
return item;
}
The NSCollectionViewItem class is thus defined:
#import "CollectionViewItem.h"
#implementation CollectionViewItem
- (instancetype)init
{
self = [super init];
if (self) {
self.identifier = #"CollectionViewItemReuseIdentifier";
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
}
#end
I wonder what piece of the puzzle is missing as these errors, while apparently clear, seem to point to something that I have implemented. Have I implemented it wrong? Or not completely?
I would be very grateful to anyone shedding some light on all this.
Thank you
The registered class should be CollectionViewItem instead of NSCollectionViewItem. Change
[collectionView registerClass:[NSCollectionViewItem class] forItemWithIdentifier:collectionViewItem.identifier];
to
[collectionView registerClass:[CollectionViewItem class] forItemWithIdentifier:collectionViewItem.identifier];
It's in the error could not load the nibName: NSCollectionViewItem, but I'm so used to NS that I didn't notice.

Modifying string content in NSTextView works under viewDidLoad method, but not under myMethod

I am trying to update the contents of an NSTextView that is connected to myViewController as a referencing outlet to the Files Owner which is the subclass myViewController.
When I use an IBAction from a button, or use the viewDidLoad method of the controller, I can update the text fine. However, when I try run the method from another class (referred to in this example as anotherViewController), it runs the method, but the textview does not change.
myViewController.h:
#import <Cocoa/Cocoa.h>
#import "anotherViewController.h"
#interface myViewController : NSViewController { }
#property (unsafe_unretained) IBOutlet NSTextView *outText;
#property (weak) IBOutlet NSButton *updateMeButton;
- (void)updateTextView:(NSString *)argText;
- (void)updateTextViewWithoutArg;
#end
myViewController.m:
#import "myViewController.h"
#interface myViewController ()
#end
#implementation myViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.outText.string = #"I work successfully";
}
- (IBAction)updateMeButton:(id)sender {
self.outText.string = #"I am updated text! I also work!";
}
- (void)updateTextView:(NSString *)argText {
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
- (void)updateTextViewWithoutArg {
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
#end
In anotherViewController.m , which has all the relevant imports, I call this:
myViewController *viewtask = [[myViewController alloc] init];
[viewtask updateTextViewWithoutArg];
Nothing happens. The method runs and logs that it should have updated, but no text updates. I have tried many different approaches, including textstorage and scrollrange methods, they all work the already working sections, but make no difference in the sections not working.
I've also tried just for fun:
myViewController *viewtask;
[viewtask updateTextViewWithoutArg];
Also using the instance variable _outText
Also using [self.outText setString:#"string"];
Also using [_outText setString:#"string"];
Again, they work but only in the already working sections.
This should be simple but isn't logical to me. In swift all I need to do is
self.outText.string = "I update whenever I'm called!"
Views you create in Interface Builder are lazily created, so if you access them before viewDidLoad is called they are nil.
If your case, calling
myViewController *viewtask = [[myViewController alloc] init];
does not cause the views to be created so when you call
[viewtask updateTextViewWithoutArg];
self.outText is nil.
You can see that this is what is happening by updating your code as below:
- (void)updateTextView:(NSString *)argText {
NSAssert(self.outText != nil, #"self.outText must not be nil");
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
you should see the assert fire.
I appear to have found a solution by making myViewController a singleton class and using sharedInstance. For this particlar app, myViewController is a debug output window and will never need to be placed in another view.
I won't accept this answer yet, as it's not the best one I'm sure. There may still be a proper solution presented that allows finding the applicable myViewController instance, and modifying the outText property attached to it. Using this singleton makes subclassing tedious as I would have to make a new class for every instance if I wanted to be able to address say 10 View Controllers.
Anyway - the way I've been able to satisfy my simple requirement:
myViewController.h:
#import <Cocoa/Cocoa.h>
#import "anotherViewController.h"
#interface myViewController : NSViewController { }
#property (unsafe_unretained) IBOutlet NSTextView *outText;
#property (weak) IBOutlet NSButton *updateMeButton;
- (void)updateTextView:(NSString *)argText;
- (void)updateTextViewWithoutArg;
+ (id)sharedInstance;
#end
myViewController.m:
#import "myViewController.h"
#interface myViewController ()
#end
#implementation myViewController
static myViewController *sharedInstance = nil;
+ (myViewController *)sharedInstance {
static myViewController *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[myViewController alloc] init];
});
return sharedInstance;
}
- (void)viewDidLoad {
[super viewDidLoad];
sharedInstance = self;
}
- (void)viewDidUnload {
sharedInstance = nil;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.outText.string = #"I work successfully";
}
- (IBAction)updateMeButton:(id)sender {
sharedInstance.outText.string = #"Button Pressed";
}
- (void)updateTextView:(NSString *)argText {
sharedInstance.outText.string = argText;
}
- (void)updateTextViewWithoutArg {
sharedInstance.outText.string = #"I make it to the TextView now";
}
#end
Now when I use this code from within anotherViewController.m it updates the right instance:
[myViewController.sharedInstance updateTextView:#"Updating with this string"];

NSPanel not showing.

My approach to this may be all wrong so I appreciate your patience.
I have a button in my main XIB file linked to this method in my document.m file:
- (IBAction)showTagModal:(id)sender {
if (!_FileTagWindowController){
_FileTagWindowController = [[FileTagWindowController alloc]init];
}
[_FileTagWindowController showWindow:self];
}
_FileTagWindowController is declared as a property in document.h and using breakpoints when the method is called, as far as I can tell is initializing properly, however _windowNibName and _window remains nil.
FileTagWindowController.h looks like this.
#import <Cocoa/Cocoa.h>
#interface FileTagWindowController : NSWindowController{
}
#property (strong) IBOutlet NSArrayController *tagsArray;
- (IBAction)saveContext:(id)sender;
#end
FileTagWindowController.m looks like this:
#import "FileTagWindowController.h"
#interface FileTagWindowController ()
#end
#implementation FileTagWindowController
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if (self) {
// Initialization code here.
}
return self;
}
- (void)windowDidLoad
{
[super windowDidLoad];
NSLog(#"Window Did Load!");
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
- (IBAction)saveContext:(id)sender {
}
#end
in my FileTagWindowController.xib I have File Owner set to FileTagWindowController as the custom class. I have the File Owner's "window" outlet linked to the window (NSPanel). That's all that should be required correct? The NSLOG statement in WindowDidLoad never gets called. I tried using [super initWithWindowNibName] in FileTagWindowController.m but that crashes not only the app, but Xcode as well with an endless initialization loop. Am I missing something obvious here?
Thanks all so much.
Try something like the following.
// document.h
#import "FileTagWindowController.h"
#property (strong) filetagWindowController *FileTagWindowController;
// document.m
#synthesize filetagWindowController;
- (IBAction)showTagModal:(id)sender {
if (self.filetagWindowController == nil) {
self.filetagWindowController = [[FileTagWindowController alloc] initWithWindowNibName:#"FileTagWindowController"];
}
[filetagWindowController showWindow:self];
[[filetagWindowController window] setReleasedWhenClosed:NO];
[NSApp runModalForWindow:filetagWindowController.window];
filetagWindowController = nil;
}
You may also want to call NSWindowWillCloseNotification to observe its state and see if filetagWindowController is closed.

Pressing a button on a custom keyboard is resulting in "Unrecognized selector being sent to instance error"

I know there are plenty of other questions addressing the same problem, but since I'm using a custom keyboard, I thought my problem would be slightly different.
This is the specific error:
-[EquationTextField element1Pressed:]: unrecognized selector sent to instance 0x4b68ee0
2012-01-02 12:23:44.630 rowQuiz[20975:207] Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[EquationTextField element1Pressed:]: unrecognized selector sent to instance 0x4b68ee0'
I have a view controller, quizController. Inside quizController is a custom view, textField (added through interface builder).
When textField is tapped, another custom view, formulaKeyboard, pops up as its keyboard. When a button on the keyboard is pressed, method element1Pressed: is called, and the error described above appears.
Some other questions say that there must be a problem with the retain count, so I tried retaining and releasing quizController in the app delegate, which didn't solve the problem.
It is also possible that I hooked up something incorrectly in Interface Builder; For the custom keyboard, File's owner and the main view are set to class elementKeyboard. For quizController, File's owner is set to quizController and hooked up to it's view.
Below is the code of the textField's class.
EquationTextField.h
#import <UIKit/UIKit.h>
#import "FormulaKeyboard.h"
#interface EquationTextField : UIView <KeyInput> {
FormulaKeyboard *keyboard;
NSString *lastElement;
}
#property (readwrite, retain) UIView *inputView;
#end
EquationTextField.m
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
self.userInteractionEnabled = YES;
[self addGestureRecognizer:
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(becomeFirstResponder)]];
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"FormulaKeyboard" owner:self options:nil];
for (id object in bundle) {
if ([object isKindOfClass:[FormulaKeyboard class]])
keyboard = (FormulaKeyboard *)object;
}
self.inputView = keyboard;
keyboard.delegate = self;
}
return self;
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
#pragma mark -- KeyInput Protocol Methods
- (void)addElement:(NSString *)elementName {
}
- (void)addCharge:(NSString *)chargeIncrease {
}
- (void) addState:(NSString *)stateName {
}
- (void)deleteCharacter {
}
- (void)dealloc
{
[super dealloc];
}
formulaKeyboard.h
#import <UIKit/UIKit.h>
#protocol KeyInput <UITextInputTraits>
- (void) addElement:(NSString*) elementName;
- (void) addCharge:(NSString*) chargeIncrease;
- (void) addState:(NSString*) stateName;
- (void) deleteCharacter;
#end
#interface FormulaKeyboard : UIView {
id <KeyInput> delegate;
}
#property (nonatomic, retain) id <KeyInput> delegate;
-(IBAction) element1Pressed:(id)sender;
-(IBAction) element2Pressed:(id)sender;
-(IBAction) element3Pressed:(id)sender;
-(IBAction) element4Pressed:(id)sender;
-(IBAction) element5Pressed:(id)sender;
-(IBAction) element6Pressed:(id)sender;
-(IBAction) chargePlusPressed:(id)sender;
-(IBAction) chargeMinusPressed:(id)sender;
-(IBAction) solidSatePressed:(id)sender;
-(IBAction) liquidStatePressed:(id)sender;
-(IBAction) gasStatePressed:(id)sender;
#end
formulaKeyboard.m
- (IBAction)element1Pressed:(id)sender {
[delegate addElement:#"Na"];
}
- (void)element2Pressed:(id)sender {
[delegate addElement:#"N"];
}
- (void)element3Pressed:(id)sender {
[delegate addElement:#"O"];
}
- (void)element4Pressed:(id)sender {
}
- (void)element5Pressed:(id)sender {
}
- (void)element6Pressed:(id)sender {
}
appDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
quizController = [[QuizController alloc] initWithNibName:#"QuizController" bundle:nil];
[self.window addSubview:quizController.view];
[self.window makeKeyAndVisible];
return YES;
}
- (void)dealloc
{
[_window release];
[quizController release];
[super dealloc];
}
The action of the keyboard's buttons are pointing to the wrong place. You've probably got them wired to File's Owner inside the FormulaKeyboard nib when they should be wired to the FormulaKeyboard object you're creating inside the nib.
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"FormulaKeyboard" owner:self options:nil];
That's being called from EquationTextField, so self would be your instance of EquationTextField. If you're keyboard's targets are pointing there, that's why you get the unrecognized selector exception.
What's happening here is a method called element1Pressed: is being sent to an instance of EquationTextField. You need to actually add the method to the class for it to work. Right now, it's sending the message to the field class, but there's no matching method, so it's throwing an error.
Also, I can't be completely sure about this, since you haven't posted the whole code and/or NIB info, but it seems that you may be going about this the wrong way. You should be using a view controller to handle everything, rather than a custom text field class. I notice that you haven't posted any code for the QuizController class. Once you do so, I may be able to give you more advice.
EDIT: Now that you've posted more code, I think I see the problem. You want the FormulaKeyboard instance to receive the event, but the event is linked to the EquationTextField instance instead. Make sure you wire it to an instance of FormulaKeyboard instead.
On the other hand, it seems that you may not have an instance of FormulaKeyboard in the NIB at all. Add an NSLog after keyboard = (FormulaKeyboard *)object to test if keyboard is ever actually assigned a value. If the NSLog doesn't fire, double-check that you've actually added an instance of FormulaKeyboard to the NIB.

Objective-C – Retained property after being set is nil?

I have two classes:
A UIViewController and a class that's subclassing NSObject that acts as a downloading helper class called OfficesParser. OfficesParser is using ASIHTTPRequest and I set the delegate for the download requests to be my UIViewController.
EDIT: Interface for the UIViewController:
#interface OfficesViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, ASIHTTPRequestDelegate> {
OfficesParser *officesParser;
}
#property (nonatomic, retain) OfficesParser *officesParser;
#end
In the UIViewController implementation I set up the OfficesParser like so:
- (void)viewDidLoad {
[super viewDidLoad];
self.officesParser = [[[OfficesParser alloc] init] autorelease]; // self.officesParser is retained
}
Then before the view appears I call my my OfficesParser object to download some data for me:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.officesParser doNetworkOperations];
}
Also in my UIViewController I have setup the appropriate delegate methods to deal with the data after it has been downloaded. In particular I'm interested in this delegate method that will run after all the data has been processed in my download queue. I can see that the delegate method is running from the log. But for some reason self.officesParser in here is nil.
- (void)queueFinished:(ASINetworkQueue *)queue {
DLog(#"queueFinished running");
[self.officesParser test]; // test will not get called because self.officesParser is nil
}