I'm trying to tweak the behavior of the default IASKAppSettingsViewController so it can provide custom subviewcontrollers (as for type = PSChildPaneSpecifier) for custom views (type = IASKCustomViewSpecifier).
I've tried to subclass IASKAppSettingsViewController without adding any new functionality to the code.
However when I load up my own subclass the settings view is completely empty - even for a bare subclass. When I switch back to the default IASKAppSettingsViewController everything works again as expected.
I've tried setting breakpoints various places in my subclass and everything seems to be working fine - except that nothing is displayed.
I've tried looking for clues in the documentation, but can't seem to find anything that explains why. The documentation for InAppSettingsKit even states that it's designed for subclassing.
What am I missing here? Any clues are appreciated - or even better a small step-by-step guide for dummies.
Updated with code:
Setting up the flipside view. It works with the standard IASKAppSettingsViewController but fails with my empty derived MyCustomSettingsViewController
- (IBAction)showInfo:(id)sender
{
// Setup navigation controller for settings view
MyCustomSettingsViewController *settings = [[MyCustomSettingsViewController alloc] init];
//IASKAppSettingsViewController *settings = [[IASKAppSettingsViewController alloc] init];
settings.delegate = self;
settings.showDoneButton = YES;
UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:settings];
navigation.navigationBar.barStyle = UIBarStyleBlack;
[settings release];
// Display the settings
navigation.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:navigation animated:YES];
[navigation release];
}
Try to add this method to your subclass:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
return [super initWithNibName:#"IASKAppSettingsView" bundle:nibBundleOrNil];
}
If you don't do that, the default implementation of IASKAppSettingsViewController kicks in and it tries to find a nib named after your class name, in this case "MyCustomSettingsViewController.nib". This probably doesn't exist.
This happens because the default implementation of -init calls -initWithNibName:nil bundle:nil and nil as the filename means to search for a default nib.
Try setting breakpoint on initialization steps and if you are adding that as a subview/ pushing on navigation controller. Use NSLogs for each method so you can find the issue. If nothing works send me the code (create a demo code using IASKAppSettingsViewController) to recerate the problem then I'll look into the issue.
Related
I've read a bunch of answers on SO already, but I'm a little confused.
I have a tab bar controller subclass I created, and in its viewDidLoad, I'm creating each of the view controllers. However, I have a dependency that I'm passing into the parent, and in turn into the view controller for each tab. I'm passing that dependency in with a custom init method (NS_DESIGNATED_INITIALIZER declared for it in the header). However, it looks like [super init] triggers viewDidLoad directly, so the dependency isn't set properly when the other view controllers are created.
Here's my custom init method:
- (instancetype)initWithSession:(T2Session *)session
{
self = [super init];
if (self) {
_session = session;
}
return self;
}
I'd like session to be set by the time I create the view controllers, but I'm sort of confused about what the best way to do this is. Any advice is much appreciated. Thanks in advance!
I've come across this situation before.
You probably sat there (like me) wishing viewDidLoad didn't get called so soon.
Anyway, this is what I settled on:
- (instancetype)initWithSession:(T2Session *)session {
if (self = [super init]) {
self.session = session;
}
return self;
}
- (void)setSession:(T2Session *)session {
_session = session;
... call the setup methods here, instead of viewDidLoad
}
At first I thought this breaks the golden rule of not calling self.xxxx from within an initializer.
However, I think that rule is only really relevant when calling methods on IBOutlets that may not have been wired up yet.
In this case, T2Session *session isn't a nib outlet.
Alternatively, if you prefer not to break that rule you could always remove the custom initializer .. and revert to using regular property-injection rather than constructor-injection. e.g.
T2Session *session = .....
MYTabBarController *tabBarController = [[MYTabBarController alloc] init];
[tabBarController setSession:session];
These are just my thoughts, hope it helps.
Today, I upgrade my Xcode to the latest version(Version 5.1 (5B130a)) to support iOS 7.1.
After doing this, I run my project in Xcode as usually. Then app crashes.
I didn't change any code before upgrading the SDK version.
The code is running perfectly in iOS 5.x, 6.x, 7.0.x.
I am simply presenting another view controller in the current view controller.
they are both initialized by storyboard.
While processing presentViewController method, it gets a error message "Thread 1: EXC_BAD_ACCESS (code=2, address=0x60)". I have checked the variables, they are both alive, not a released garbage.
What's the problem with iOS 7.1??
the project is using non-ARC mechanism.
Here is my code:
#property (nonatomic, retain) ArticleViewController *articleView;
....
self.articleView = [[UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil] instantiateViewControllerWithIdentifier:#"ArticleViewController"];
...
[self presentViewController:self.articleView animated:NO completion:^() {
log(#"has shown article page...");
}];
but it works fine if presenting another view by using addSubView function:
[self.view addSubView:self.articleView.view];
I really don't know why this happens.
This happened to my app while presenting a view controller with modalPresentationStyle = UIModalPresentationCustom;
Here's how my code looks like on iOS 7.0:
//Inside my MyPresentedViewController:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
[self setupContent];
}
return self;
}
- (void)setupContent
{
//TransitionManager adopts UIViewControllerTransitioningDelegate and UIViewControllerAnimatedTransitioning
TransitionManager *transitionManager = [[TransitionManager alloc] init];
transitionManager.presenting = YES;
self.modalPresentationStyle = UIModalPresentationCustom;
self.transitioningDelegate = transitionManager;
}
However, the above code crashes on iOS 7.1 so I changed the implementation to:
#interface MyPresentedViewController
#property (nonatomic, strong) TransitionManager *transitionManager;
#end
...
//Inside my MyPresentedViewController:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
[self setupContent];
}
return self;
}
- (void)setupContent
{
//TransitionManager adopts UIViewControllerTransitioningDelegate and UIViewControllerAnimatedTransitioning
self.transitionManager = [[TransitionManager alloc] init];
_transitionManager.presenting = YES;
self.modalPresentationStyle = UIModalPresentationCustom;
self.transitioningDelegate = _transitionManager;
}
Basically, instead of declaring a transitionManager object inside the setupContent method, I created a private property (strong reference) for it.
Hm the only thing I can think of is ensuring that your articleView property is a strong reference.
#property (nonatomic, strong) ArticleViewController *articleView;
I ran into a similar issue with a picker view. In my case the pickerview was set to nil after the selection handler block, although this caused no issues as recently as last night, it was causing the app to crash this morning. Removing that line fixed the issue, however I have converted this project to ARC in the last month so you may have to find a better way to handle clean up.
This may be completely unrelated to the stated problem or answers but I ran into a similar problem when accessing an NSString on an iPhone 5S. The exact same code runs fine on an iPad and iPad at the same time.
To start out with I think it's important to state that I'm running under ARC, and I have been told repeated not to "release" any objects I instantiate in my functions. I've had problems with this before so I use some of my C# background to set almost all of my local variables to nil (null in C#). Yes, I don't trust MS GC either.
Back to the problem at hand; I have an NSString called 'data'. 'data' is read as a result from another method in a different class. Using NSLog I can see the contents of 'data'. On the next line I convert 'data' to an array to use it in scanf. That still works. On the third line I try to NSLog 'data' again, but then I get the EXC_BAD_ACCESS error. Each time it has a different address. I'm even less comfortable with ARC than I am with the Microsoft Garbage Collector so I wrapped the function in a try..catch..finally. 'data' is now sitting outside of the try..catch..finally. I'm setting 'data' to nil in the finally, which seems to fix the problem.
I know this was a bit long winded, but I would really appreciate it someone could explain why this would happen. I'm expecting to see a lot of these problems to popup all over my code now.
I am using XCtest to test the title of a view. Trying to get into the habit of writing tests first. Setup looks like
- (void)setUp
{
[super setUp];
self.appDelegate = [[UIApplication sharedApplication] delegate];
self.tipViewController = self.appDelegate.tipViewController;
self.tipView = self.tipViewController.view;
self.settingsViewController = self.appDelegate.settingsViewController;
self.settingsView = self.settingsViewController.view;
}
The problem is "settingsViewController". I have two functions for the actual test:
- (void) testTitleOfMainView{
XCTAssertTrue([self.tipViewController.title isEqualToString:#"Tip Calculator"], #"The title should be Tip Calculator");
//why does this not work?
// XCTAssertEqual(self.tipViewController.title, #"Tip Calculator", #"The title should be Tip Calculator");
}
- (void) testTitleOfSettingsView{
//make the setttings view visible
[self.tipViewController onSettingsButton];
//test the title
XCTAssertTrue([self.settingsViewController.title isEqualToString:#"Settings"], #"The title should be Settings");
}
The "testTitleOfMainView" works. But the "testTitleOfSettingsView fails as self.settingsViewController is nil. I can sort of understand why. The view has not been initialized as yet. So I tried sending the message to the main controller that brings the settignscontroller in view
[self.tipViewController onSettingsButton];
The settingsController is still nil. Should I be using mocks? Somebody suggested this for my other question
xctest - how to test if a new view loads on a button press
Should I subclass the settingsview and bring it up manually? Thank you.
Stay away from actually loading views in a real navigation stack. Real UI interactions typically need the run loop to receive events, so they will not work in a fast unit test. So throw away your setUp code.
Instead, instantiate the view controller on its own, and have it load:
- (void)testTitleOfSettingsView
{
SettingsViewController *sut = [[SettingsViewController alloc] init];
[sut view]; // Accessing the view causes it to load
XCTAssertEquals(#"Settings", sut.title);
}
Also, learn the various assertions that are available to you in XCTest, not just XCAssertTrue. Avoid comments in those assertions; a single assertion in a small test should speak for itself.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
fetched = NO;
self.title = #"Nearby";
// Custom initialization
liked = NO;
categories = [[NSArray alloc] init];
fundings = [[NSArray alloc] init];
stages = [[NSArray alloc] init];
}
return self;
}
I use the code above. am I during anything wrong by initializing all of my varialbes in the initWithNibName function? Is that the correct place to do so?
You will probably not run into issues, but you can use a ViewController also with the pure init in which case the NIB is not loaded and also your initialization will not be called. Another place to make the init for that is the viewDidLoad which is called only after the init.
You can do it in the init method, that's fine. You could also do it (or some of) in the viewDidLoad method. The difference would (generally) be that viewDidLoad is called when your view is actually getting ready to be shown, so you need everything setup. init on the other hand could be called well in advance. There isn't a hard and fast answer, it depends what the items you're setting up are and if they can be unloaded when the view isn't on display. With newer versions of iOS that concern changes anyway as the view generally isn't unloaded. You really need to read about the view and controller life cycles and decide when to create and destroy your attributes.
I've asked a question about the same issue before, and the solutions worked, but it was not a compatible iOS 4.3 solution, and I thought my design is not the right one.
Now I want to show a MFMailComposeView(Controller) as a modal view on top of my RootView(Controller) when i press a button. And instead of making it the delegate i want to make a simple NSObject which implements the protocol.
Who is also capable to show the MFMailComposeView(Controller) in the RootViewController.
I am trying this design/solution which gives me memory allocation/access problems.
RootViewController.m:
- (IBAction)tapExportButton:(id)sender
{
SendMailController *sendMailController = [[SendMailController alloc]initWithParentViewController:self];
[sendMailController openMailDialog];
[sendMailController release];
}
SendMailController.h
#interface SendMailController : NSObject <MFMailComposeViewControllerDelegate>
- (id)initWithParentViewController:(UIViewController *)parentViewController;
- (void)openMailDialog;
#property (retain, nonatomic) UIViewController* parentViewController;
#end
SendMailController.m
#import "SendMailViewController.h"
#implementation SendMailController
#synthesize parentViewController = _parentViewController;
- (id)initWithParentViewController:(UIViewController *)parentViewController
{
if (self=[super init]) {
self.parentViewController = parentViewController;
}
return self;
}
- (void) dealloc
{
self.parentViewController = nil;
[super dealloc];
}
- (void)openMailDialog
{
if ([MFMailComposeViewController canSendMail])
{
MFMailComposeViewController *mailer = [[MFMailComposeViewController alloc] init];
mailer.mailComposeDelegate = self;
...
mailer.modalPresentationStyle = UIModalPresentationPageSheet;
[self.parentViewController presentModalViewController:mailer animated:YES];
[mailer release];
}
}
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
switch (result)
...
// Remove the mail view
[controller.parentViewController dismissModalViewControllerAnimated:YES];
}
#end
When I set a breakpoint in the delegation method, it crashes already before that.
Is is something with the delegate property of mailer (MFMailComposeViewController)?
The problem is that you create your instance of SendMailController and try to show the composer view, and then you release the SendMailController. This causes it to be deallocated. It looks like it works because the composer view is on screen - this is because it has been retained by the presentModalViewController call.
To fix, you need to retain your instance of SendMailController and release it when the composer has been dismissed.
The correct way to do it (and required if you use ARC, and you should use ARC) is to provide a delegate callback to tell the owner that it's finished - which kind of makes the class pointless if all it does is wrap the composer.
The cheating way (which only works when not using ARC, and which you need to be very careful with) is to have your object retain itself when it presents the composer and release itself when the composer is dismissed.
The underlying problem being your root view controller containing all the logic, you should look at using child view controllers (if a single screen holds all your UI). Usually though, your root view should be the simple class (like a master list of options) and the views it presents would be more complex (the detail views). You need to look at ensuring that the appropriate class owns the responsibility for each screen of UI.