I am using Xcode 4.5, targeting iOS5 and above.
I have a popover that allows a user to change the fonts of the underlying view.
When tapping on the font, the change does not occur, until after I close the popover and the underling view and reopen.
It is set up for delegation. And the receiving view does import the the FontsPopoverViewDelegate.
Any help toward a solution would be greatly appreciated.
The Delegate methods for fonts:
#protocol FontsPopoverViewDelegate <NSObject>
- (void)fontResize:(float)size forView:(int)type;
- (void)font:(int)fontID forView:(int)fView;
- (int)getFontForView:(int)fView;
- (float)getFontSizeForView:(int)fView;
#end
Methods implemented in underlying view:
- (void)fontResize:(float)size forView:(int)type {
fontSizes[type] = size;
[self invalidate];
}
- (void)font:(int)fontID forView:(int)fView {
fontIds[fView] = fontID;
[self invalidate];
}
- (int)getFontForView:(int)fView {
return fontIds[fView];
[self invalidate];
}
- (float)getFontSizeForView:(int)fView {
return fontSizes[fView];
[self invalidate]; // added to spark a reaction from the view
}
-(void) invalidate {
NSLog(#"Invalidate called");
[self saveTextChanges];
[self refreshBodyText];
[self refreshBackground];
[self refreshBodyText];
[self refreshDateFont];
[self refreshTitleFont];
}
Any help would be greatly appreciated.
I solved this by posting a notification from the font popover to the underlying view. In the underlying view, I added the call... [self viewWillAppear:YES];
Now, it is working perfectly.
I have a slider, within the Fonts popover, that changes the size and a notification and calling viewWillAppear within the notification works the same as the font change.
- (void)aFontChanged:(NSNotification *)notification
{
NSLog(#"aFontChanged notification?");
[self viewWillAppear:YES];
[self refreshBodyText];
[self refreshDateFont];
[self refreshTitleFont];
}
- (void)aFontSizeChanged:(NSNotification *)notification
{
NSLog(#"aFontSizeChanged notification?");
[self viewWillAppear:YES];
[self refreshBodyText];
[self refreshDateFont];
[self refreshTitleFont];
}
Related
I just got a crash showing as the following screen shot, it happened when I click the back button on the navigation bar, is any typical situation will cause this crash?
In my experience there was an issue introduced in iOS 7 making it possible for you to start a transition before another has ended, which ultimately causes this crash. You can reproduce this manually if you put 2 navigation calls back to back and run them, such as:
[self.navigationController pushViewController:whatever animated:YES];
[self.navigationController pushViewController:whatever2 animated:YES];
If you do this, you will eventually see that crash occur.
The easiest way I found to make sure this never happens is by subclassing UINavigationController and implementing the UINavigationControllerDelegate to prevent overlapping transitions.
Once I began using the code below, the number of crashes I see due to this issue has dropped to 0.
One thing to note is that if you actually need to implement another <UINavigationControllerDelegate> you will need to write some code to store the extra delegate yourself and pass on the delegate calls, perhaps using NSProxy or something like that.
#interface MyNavigationController () <UINavigationControllerDelegate>
{
// used to prevent "can't add self as subview" crashes which occur when trying to animate 2 transitions simultaneously
BOOL _currentlyAnimating;
}
#end
#implementation MyNavigationController
- (void) viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
}
- (void) pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if(_currentlyAnimating)
{
return;
}
else if(animated)
{
_currentlyAnimating = YES;
}
[super pushViewController:viewController animated:animated];
}
- (UIViewController *) popViewControllerAnimated:(BOOL)animated
{
if(_currentlyAnimating)
{
return nil;
}
else if(animated)
{
_currentlyAnimating = YES;
}
return [super popViewControllerAnimated:animated];
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
_currentlyAnimating = NO;
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// tracking cancelled interactive pop
// http://stackoverflow.com/questions/23484310/canceling-interactive-uinavigationcontroller-pop-gesture-does-not-call-uinavigat
[[self transitionCoordinator] notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
if([context isCancelled])
{
UIViewController *fromViewController = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
[self navigationController:navigationController willShowViewController:fromViewController animated:animated];
if([self respondsToSelector:#selector(navigationController:didShowViewController:animated:)])
{
NSTimeInterval animationCompletion = [context transitionDuration] * [context percentComplete];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (uint64_t)animationCompletion * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self navigationController:navigationController didShowViewController:fromViewController animated:animated];
});
}
}
}];
}
#end
I'm having trouble switching between two subclassed CollectionViewFlowLayouts.
I call the following method in my collectionViewController:
header:
#property (nonatomic, strong) PortraitFlowLayout *portraitFlowLayout;
#property (nonatomic, strong) LandscapeFlowLayout *landscapeFlowLayout;
Implementation:
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
if (UIDeviceOrientationIsPortrait(toInterfaceOrientation)) {
self.landscapeFlowLayout = nil;
[self portraitFlowLayout];
NSLog(#"Orientation portrait");
} else {
self.portraitFlowLayout = nil;
[self landscapeFlowLayout];
NSLog(#"Orientation landscape");
}
[self.collectionView.collectionViewLayout invalidateLayout];
}
and in the same collectionViewController:
- (LandscapeFlowLayout *)landscapeFlowLayout
{
if (_landscapeFlowLayout == nil) {
_landscapeFlowLayout = [[LandscapeFlowLayout alloc] init];
self.collectionView.collectionViewLayout = _landscapeFlowLayout;
}
[self.collectionView.collectionViewLayout invalidateLayout];
return _landscapeFlowLayout;
}
- (PortraitFlowLayout *)portraitFlowLayout
{
if (_portraitFlowLayout == nil) {
_portraitFlowLayout = [[PortraitFlowLayout alloc] init];
self.collectionView.collectionViewLayout = _portraitFlowLayout;
}
[self.collectionView.collectionViewLayout invalidateLayout];
return _portraitFlowLayout;
}
I know that both layout are valid, and working, since I'm pushing into this viewController form another viewCont, and I've tried to do it with both the landscape and the portrait layout, which works fine.
The problem arises when I change the orientation. The first orientation change is fine, and the layout change as it's supposed to. But when it's then rotated back (sometimes it will rotate back and forth a few times before crashing), it gives me the following error when I trace it with the Zombie template in Instruments:
How can I trace this error further? Or, fix the problem? Any help is greatly appreciated.
Thanks in advance
EDIT
The problem seems only to arise when rotating to portrait.
Chris
Try to use UIDeviceOrientationDidChangeNotification instead willRotateToInterfaceOrientation.
This lines from the Apple's docs:
To support an alternate landscape interface, you must do the following:
Implement two view controller objects. One to present a portrait-only interface, and the other to present a landscape-only interface.
Register for the UIDeviceOrientationDidChangeNotification notification. In your handler method, present or dismiss the alternate view controller based on the current device orientation.
- (void)awakeFromNib
{
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
- (void)orientationChanged:(NSNotification *)notification
{
//without selector event may be lost
[self performSelector:#selector(updateFrameWithOrientation) withObject:nil afterDelay:0];
}
-(void) updateFrameWithOrientation{
UIInterfaceOrientation deviceOrientation = [UIApplication sharedApplication].statusBarOrientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation))
{
}
else if (UIDeviceOrientationIsPortrait(deviceOrientation))
{
}
[self.collectionView reloadData];
}
I have written my custom UIAlertview to allow auto dismissal in certain cases. Now, with iOS 7 when auto dismissal happens the tint color of my nav bar changes. As per the iOS7 Transition Guide:
https://developer.apple.com/library/ios/documentation/userexperience/conceptual/transitionguide/TransitionGuide.pdf
When an alert or action sheet appears, iOS 7 automatically dims the tint color of the views behind it. To respond to this color change, a custom view subclass that uses tintColor in its rendering should override tintColorDidChange to refresh the rendering when appropriate.
Any idea if this can be handled from with in the custom UIAlertView only. Below is my code for custom UIAlertView:
#define kStartupFailAlert 203
#import "RunnerUIAlertView.h"
#implementation RunnerUIAlertView
- (id)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(removeAlert) name:kRemoveVisibleAlert object:nil];
}
return self;
}
- (void)removeAlert {
if (self.tag != kStartupFailAlert) { // If not kRunnerStartupFailAlert - as it will be auto dismissed
self.delegate = nil;
NSInteger aCancelButtonIndex = [self cancelButtonIndex];
[super dismissWithClickedButtonIndex:aCancelButtonIndex animated:NO];
}
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#end
Got this worked out by restoring the tint settings on app delegate's window. But then it has this side effect of not dimming the navigation tint color when opening a popover or a sheet. It seems be an issue with iOS7 SDK.
- (void)removeAlert {
if (self.tag != kStartupFailAlert) { // If not kRunnerStartupFailAlert - as it will be auto dismissed
self.delegate = nil;
NSInteger aCancelButtonIndex = [self cancelButtonIndex];
[self dismissWithClickedButtonIndex:aCancelButtonIndex animated:NO];
MyAppDelegate *appDeletgate = [Utilities applicationDelegate];
appDeletgate.window.tintAdjustmentMode = UIViewTintAdjustmentModeNormal;
}
}
I have an app that uses a xib defined AdBannerView. If the app runs on an iPhone (4 or 5) everything works as expected, ads get shown, banners get hidden / shown etc.
However if the app is run on an iPad it crashes after repeatedly failing to receive the ad. Examining the call stack shows repeated calls to bannerView:didFailToReceiveAdWithError:
Watching allocations while its running on an iPad shows continuous memory growth until the crash.
Messing with the network connectivity doesn't seem to alter the fact that it works on an iPhone but not on an iPad.
I read this SO question which instead of using a AdBannerView in the xib it creates it on the fly and then releases it appropriately when the ad fails to load.
EDIT:
I changed the devices setting in the project file from iPhone to Universal. The app now does not crash but of course all the views are now 'messed up'. So one option for a fix would be to implement the iPad idiom throughout the app.
My questions are -
What is going on? No, really! I'm confused as to why there is differing behaviour between devices.
Should I look to creating the AdBannerView programmatically? That kind of feels defeatist.
How can I fix this behaviour?
Here is the code
#pragma mark ADBannerViewDelegate
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
[self showBanner];
}
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
[self hideBanner];
}
- (void)bannerViewActionDidFinish:(ADBannerView *)banner
{
[self hideBanner];
}
#pragma mark ADBanner helpers
- (void)hideBanner
{
CGRect hiddenFrame = self.bannerDisplayFrame;
hiddenFrame.origin.y = self.view.frame.size.height;
[UIView animateWithDuration:0.3f
animations:^{
[self.adBannerView setFrame:hiddenFrame];
}
completion:^(BOOL finished)
{
if (finished)
{
[self.adBannerView setAlpha:0.0f];
}
}];
}
- (void)showBanner
{
[self.adBannerView setAlpha:1.0f];
[UIView animateWithDuration:0.3f
animations:^{
[self.adBannerView setFrame:self.bannerDisplayFrame];
}
completion:^(BOOL finished)
{
if (finished)
{
[NSTimer scheduledTimerWithTimeInterval:60.0f target:self selector:#selector(hideBanner) userInfo:nil repeats:NO];
}
}];
}
It looks like you are creating new iAD banner views every time, the suggested way is to use a shared one throughout the whole app. This might be the reason of continuous memory growth in your app and you will definitely end up with a warning from apple servers if you request ads too many times. Have a look at here in Apple's documentation for more details iAD Best Practices
This is how I implemented shared adbannerview, it might be of help.
AppDelegate.h
#property (nonatomic, strong) ADBannerView *adBanner;
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
adBanner = [[ADBannerView alloc] initWithFrame:CGRectZero];
adBanner.delegate = self;
adBanner.backgroundColor = [UIColor clearColor];
adBanner.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
...
}
prefix.pch or better in a header file included in prefix.pch
#define SharedAdBannerView ((AppDelegate *)[[UIApplication sharedApplication] delegate]).adBanner
And I have a implemented a uiviewcontroller category to handle iADs
#implementation UIViewController (SupportIAD)
-(void)bannerViewDidLoadAd:(ADBannerView *)banner
{
SharedAdBannerView.hidden = FALSE;
}
-(void) bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
SharedAdBannerView.hidden = TRUE;
}
//This method adds shared adbannerview to the current view and sets its location to bottom of screen
//Should work on all devices
-(void) addADBannerViewToBottom
{
SharedAdBannerView.delegate = self;
//Position banner just below the screen
SharedAdBannerView.frame = CGRectMake(0, self.view.bounds.size.height, 0, 0);
//Height will be automatically set, raise the view by its own height
SharedAdBannerView.frame = CGRectOffset(SharedAdBannerView.frame, 0, -SharedAdBannerView.frame.size.height);
[self.view addSubview:SharedAdBannerView];
}
-(void) removeADBannerView
{
SharedAdBannerView.delegate = nil;
[SharedAdBannerView removeFromSuperview];
}
#end
And now in every viewcontroller that is going to display iADs, import the category and in viewDidLoad:
- (void)viewDidLoad
{
...
[self removeADBannerView];
[self addADBannerViewToBottom];
...
}
To prevent the views getting messed up, try turning "Auto Layout" off.
Xcode 4.5 corrupting XIBs?
I have solved this problem.
The root of the problem is that iPad's view property of iPad's UIViewController looping recursively.
All you need is to break infinite call.
In my case I just add these lines:
if (_banner.alpha == 0)
{
return;
}
in banner hiding method.
I guess you have crash here:
hiddenFrame.origin.y = self.view.frame.size.height;
Anyway, its not a good approach do not check property before changing.
So let me clarify first off with showing what code i have implemented
-(BOOL)canBecomeFirstResponder
{
return YES;
}
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self becomeFirstResponder];
}
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated
{
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake)
{
NSLog(#"FUUU");
}
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake)
{
NSLog(#"FUUU");
}
}
this is all in a UIViewController class
and in - (void) applicationDidFinishLaunching:(UIApplication*)application i have set
[application setApplicationSupportsShakeToEdit:YES];
Yet it does nothing, not nary single motion detected. I'm not sure what else to do. This seems to have worked for many other people, so i am baffled as to why i am different...Could it be because it's through a UINavigationController? or because i load the main application via a plist and my main looks like so
retVal = UIApplicationMain(argc, argv, nil, nil);
I am quite thoroughly stumped.
In order to detect shake gesture you need to subclass the UIView and implement the following methods (Do not implement these methods on UIViewController):
(BOOL)canBecomeFirstResponder
(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
Then add the subclassed view to UIViewController and make it first responder by calling -becomeFirstResponder on it.
You don't need to set [application setApplicationSupportsShakeToEdit:YES]; because this is the default value and iOS use this only for Shake to Undo/Redo.
If you want to capture the motion in your view controller you need to set this property to NO [application setApplicationSupportsShakeToEdit:NO]; and handle it by yourself.
As described in Event Handling Programming Guide.
Hope this helps.
PS: Just in case, you call the wrong super in your viewDidAppear method. This should be :
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
Thank you all for your responses but i ended up using a different method i accessed the accelerometer in the app delegate and then send a nsnotifcation via the nsnotification center to the current ui navigation controller displayed
- (void) accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:nil userInfo:nil];
}