I am experiencing a rather serious issue with my iPhone app using ARC.
I have a viewcontroller (lets call this A). This viewcontroller opens a navigationcontroller as a modal which runs through 3 different viewcontrollers (lets call these 1, 2 and 3). After viewing number 3 the navigationcontroller closes and we're back to A again.
So the flow is: A opens navigationcontroller and goes through 1->2->3 and then it closes again.
Every time I go through this flow i lose memory. I've searched through all my files looking for any retain og strong properties, un-invalidated timers or similar in order to solve this problem.
I have one idea, which might be the problem. At viewcontroller 1 i present a animation using coreanimation and a sprite. I'm using a implementation made by someone else. It seems like if i disable the animations the memory used seems quite constant (and thereby no memory loss). I have modified the implementation a bit to use ARC. This is the implementation I use for my sprite animations:
MCSpriteLayer.h
//
// MCSpriteLayer.h
//
// Created by Miguel Angel Friginal on 8/20/10.
// Copyright 2010 Mystery Coconut Games. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#interface MCSpriteLayer : CALayer {
unsigned int sampleIndex;
}
// SampleIndex needs to be > 0
#property (nonatomic) unsigned int sampleIndex;
// For use with sample rects set by the delegate
+ (id)layerWithImage:(CGImageRef)img;
- (id)initWithImage:(CGImageRef)img;
// If all samples are the same size
+ (id)layerWithImage:(CGImageRef)img sampleSize:(CGSize)size :(int)useRetina;
- (id)initWithImage:(CGImageRef)img sampleSize:(CGSize)size;
// Use this method instead of sprite.sampleIndex to obtain the index currently displayed on screen
- (unsigned int)currentSampleIndex;
#end
MCSpriteLayer.m
//
// MCSpriteLayer.m
//
// Created by Miguel Angel Friginal on 8/20/10.
// Copyright 2010 Mystery Coconut Games. All rights reserved.
//
#import "MCSpriteLayer.h"
#implementation MCSpriteLayer
#synthesize sampleIndex;
#pragma mark -
#pragma mark Initialization, variable sample size
- (id)initWithImage:(CGImageRef)img;
{
self = [super init];
if (self != nil)
{
self.contents = (__bridge id)img;
sampleIndex = 1;
}
return self;
}
+ (id)layerWithImage:(CGImageRef)img;
{
MCSpriteLayer *layer = [(MCSpriteLayer*)[self alloc] initWithImage:img];
return layer;
}
#pragma mark -
#pragma mark Initialization, fixed sample size
- (id)initWithImage:(CGImageRef)img sampleSize:(CGSize)size;
{
self = [self initWithImage:img];
if (self != nil)
{
CGSize sampleSizeNormalized = CGSizeMake(size.width/CGImageGetWidth(img), size.height/CGImageGetHeight(img));
self.bounds = CGRectMake( 0, 0, size.width, size.height );
self.contentsRect = CGRectMake( 0, 0, sampleSizeNormalized.width, sampleSizeNormalized.height );
}
return self;
}
+ (id)layerWithImage:(CGImageRef)img sampleSize:(CGSize)size :(int)useRetina;
{
CGSize newSampleSize;
if(useRetina == 1) {
// Supporting retina displays
if ([[UIScreen mainScreen] respondsToSelector:#selector(displayLinkWithTarget:selector:)] &&
([UIScreen mainScreen].scale == 2.0)) {
newSampleSize = CGSizeMake(size.width*2, size.height*2);
} else {
newSampleSize = size;
}
} else
newSampleSize = size;
MCSpriteLayer *layer = [[self alloc] initWithImage:img sampleSize:newSampleSize];
return layer;
}
#pragma mark -
#pragma mark Frame by frame animation
+ (BOOL)needsDisplayForKey:(NSString *)key;
{
return [key isEqualToString:#"sampleIndex"];
}
// contentsRect or bounds changes are not animated
+ (id < CAAction >)defaultActionForKey:(NSString *)aKey;
{
if ([aKey isEqualToString:#"contentsRect"] || [aKey isEqualToString:#"bounds"])
return (id < CAAction >)[NSNull null];
return [super defaultActionForKey:aKey];
}
- (unsigned int)currentSampleIndex;
{
return ((MCSpriteLayer*)[self presentationLayer]).sampleIndex;
}
// Implement displayLayer: on the delegate to override how sample rectangles are calculated; remember to use currentSampleIndex, ignore sampleIndex == 0, and set the layer's bounds
- (void)display;
{
if ([self.delegate respondsToSelector:#selector(displayLayer:)])
{
[self.delegate displayLayer:self];
return;
}
unsigned int currentSampleIndex = [self currentSampleIndex];
if (!currentSampleIndex)
return;
CGSize sampleSize = self.contentsRect.size;
self.contentsRect = CGRectMake(
((currentSampleIndex - 1) % (int)(1/sampleSize.width)) * sampleSize.width,
((currentSampleIndex - 1) / (int)(1/sampleSize.width)) * sampleSize.height,
sampleSize.width, sampleSize.height
);
}
#end
Is this implementation somehow not realeasing correctly or retaining anything? Thanks in advance.
Update
- I am using Instruments to measure the memory. I am using the Memory Monitor where I keep an eye on the Physical Memory Free
- The image is created like this:
NSString *path = [[NSBundle mainBundle] pathForResource:#"round_start.png" ofType:nil];
CGSize fixedSize = CGSizeMake(320, 480);
mascot = [MCSpriteLayer layerWithImage:[UIImage imageWithContentsOfFile:path].CGImage sampleSize:fixedSize :0];
mascot.frame = CGRectMake(ANIMATION_X, ANIMATION_Y, ANIMATION_WIDTH, ANIMATION_HEIGHT);
[self.view.layer addSublayer:mascot2];
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"sampleIndex"];
anim.delegate = self;
anim.fromValue = [NSNumber numberWithInt:1];
anim.toValue = [NSNumber numberWithInt:52];
anim.duration = ANIMATION_DURATION;
anim.repeatCount = 1;
[mascot addAnimation:anim forKey:nil];
- I've been experiencing with closing the modal with
[self dismissModalViewControllerAnimated:YES];
and
[self.navigationController dismissModalViewControllerAnimated:YES];
Dismissing the navigation controller does not release it, assuming you have a strong reference to it (that would need to get nil'd). In the view controllers used by it, add a log message in the dealloc method, so you know they are getting dealloced (you can do this for any subclassed item). If needed you can create a simple UINavigation subclass for the sole purpose of adding a message in dealloc). You will find that one or more of these items are not getting dealloced, and then you need to figure out if they are retained by a property/ivar or because they still have a superview.
Related
If I define a constant as an integer, how is it recognised as a float ?
I built and ran the code below without problems under Xcode 6.1.1. The problem only appeared after I upgraded to Xcode 7.2 (7C68) and El Capitan.
iOSCircleView.m
int const SECTORS_80 = 80;
#implementation iOSCircleView
- (void)ViewSettings_OverView {
sectors = SECTORS_80;
NSLog(#"sectors %i", sectors);
}
When I run it NSLog reports a warning
Format specifies type ‘int’ but the argument has type ‘float’
The console displays
2016-01-07 10:05:00.210 AutoDrawUI[1298:555384] sectors 855670784
If I change NSLog command to
NSLog(#"sectors %f”, sectors);
and rerun the code, there is no warning but the console displays
2016-01-07 10:38:01.547 AutoDrawUI[1330:632806] sectors 80.000000
This seems like a bug. But maybe I’ve missed something
Here is the Original Code
int const SECTORS_80 = 80;
#implementation iOSCircleView
// Initialisation
// frame a view for drawing
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
totalCircles = [[NSMutableArray alloc] init];
centre = [[NSMutableArray alloc] init];
ring = [[NSMutableArray alloc] init];
// Set background color
self.backgroundColor = [UIColor grayColor];
}
return self;
}
// initialise single target view (7 others are commented out)
- (void)drawRect:(CGRect)rect {
[self ViewSettings_OverView]; // 1. "launch view" ??
}
Running (note: the problem occurs before it gets here)
// run
- (void)autoDrawUI {
[self drawCircle];
}
Here is the full class (unedited)
// initialise display settings
- (void)ViewSettings_OverView {
viewState = 0;
// number of dots on perimeter
sectors = SECTORS_80; // WARNING HERE
limit = sectors;;
if (sectors <= 0) { // WARNING HERE
show = FALSE; // disable
} else
show = TRUE; // enable perimeter dots
// radius of large circle perimeter
uberRadius = UBERRADIUS_52;
// radius of dots on large circle perimeter
dotRadius = DOTRADIUS_4;
dotsFilled = TRUE; // outline
oneColour = TRUE; // every colour
alternateDots = TRUE; // alternately filled and coloured
oddEvenState = 1; // PlayView
ringDot = RINGDOT_HIDE; //_SHOW104
centreDotFilled = FALSE; // fill or outline
centreDot = CENTREDOT_HIDE; //CENTREDOT_SHOW26;
centreDotFilled = TRUE; // fill or outline
NSLog(#"View %i", viewState);
NSLog(#"sectors %i", sectors); // WARNING HERE
NSLog(#"show %i", show);
[self centreReference];
}
And here is the .h file
#import <UIKit/UIKit.h>
#interface iOSCircleView : UIView
{
NSMutableArray *totalCircles;
NSMutableArray *sectorDots;
// following Stackoverflow 08/02/2015 ?
NSMutableArray *centre; // 1 of 5 (Global View), centre (Wait View), even bell icon (Play View)
NSMutableArray *ring; // odd bell icon (Play View)
// TO DO
NSMutableArray *sectorDotsCyan; // 16 moons surround 1 of 5 buttons on Global View
NSMutableArray *sectorDotsRed; // 16 moons 2
NSMutableArray *sectorDotsYellow; // 16 moons 3
NSMutableArray *sectorDotsMagenta; // 16 moons 4
NSMutableArray *sectorDotsGreen; // 16 moons 5
int dotCount, // working
colourIndex, // working
toggleIndex, // working
imageIndex,
limit, // working
selectedZone, // working
selectedPhone,
selectedState,
state,
viewState, // working
ringDotFilled,
centreDotFilled, // working
dotsFilled, // working
labelShown,
oneColour, // working
alternateDots,
i, // working
show; // working
BOOL shakeTap, // PlayView
oddEvenPhone, // LocalView
oddEvenState; // working
float uberX, // working
uberY, // working
uberRadius, // working
uberAngle, // working
labelX,
labelY,
dotRadius, // working
sectors, // working
x, // working
y, // working
ringDot, // working
centreDot, // working
textOffset, // working
startAngle,
dotAngle,
endAngle,
angle;
CGPoint dotPosition;
CGRect boxBoundary;
CGContextRef context;
}
#property(nonatomic,retain) UIColor* fillColor;
#property(nonatomic,retain) UIColor* circle;
#property(nonatomic,retain) UIImage* anImage;
- (void)drawCircle;
- (void)drawRosette;
#end
... and the problem becomes immediately obvious. I declared sectors as float and somehow how I got away with this previously. My brain's still on holiday!
Your issue has nothing to do with SECTORS_80. It's all about the sectors variable since that is the variable you are logging. You didn't specify how it is declared but clearly the error indicates that you declared it as a float.
Change:
float sectors;
to:
int sectors;
Or change the format to use %f. Either way will remove the error. But only one is really appropriate depending on your needs.
I'm using MapBox and RMTileCache's beginBackgroundCacheForTileSource: to preload tiles before rendering maps in my client app. I want to preload the tiles for the currently visible area of the map from zoom level 3 thru zoom level 7. So, I pass the current bounds of my mapView using the rect returned from [mapView latitudeLongitudeBoundingBox]. My ViewController implements RMTileCacheBackgroundDelegate so as to get callbacks as the caching occurs and then completes. I do receive callbacks on didBeginBackgroundCacheWithCount:forTileSource: and didBackgroundCacheTile:withIndex:ofTotalTileCount: However, tileCacheDidFinishBackgroundCache: is never called.
I stepped thru the source of RMTileCache and noticed that the actual count of cached tiles in "progTile" never reaches the calculated total count in "totalTiles". Therefore, the final callback, tileCacheDidFinishBackgroundCache:, is not reached.
I'm not sure how to change (or if I should change) the totalTiles calculation. Perhaps I am passing the wrong viewing rect in my initial call? I'm not totally clear if that is correct. I can create a simple fix that calls tileCacheDidFinishBackgroundCache: in the case that caching simply finishes, but this seems to just hide the issue. Any guidance here would be appreciated.
For reference, my test code is straight forward:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
RMMapboxSource *onlineSource = [[RMMapboxSource alloc] initWithMapID:#"appleweed.control-room"];
mapView = [[RMMapView alloc] initWithFrame:self.view.bounds andTilesource:onlineSource];
//mapView.delegate = self;
mapView.bouncingEnabled = YES;
mapView.clusteringEnabled = YES;
mapView.clusterMarkerSize = CGSizeMake(40, 40);
mapView.clusterAreaSize = CGSizeMake(40, 40);
mapView.zoom = 1;
RMSphericalTrapezium rect = [mapView latitudeLongitudeBoundingBox];
mapView.tileCache.backgroundCacheDelegate = self;
[mapView.tileCache beginBackgroundCacheForTileSource:mapView.tileSource
southWest:rect.southWest
northEast:rect.northEast
minZoom:3.0
maxZoom:7.0];
}
- (void)tileCache:(RMTileCache *)tileCache didBeginBackgroundCacheWithCount:(int)tileCount forTileSource:(id <RMTileSource>)tileSource {
NSLog(#"start");
}
- (void)tileCache:(RMTileCache *)tileCache didBackgroundCacheTile:(RMTile)tile withIndex:(int)tileIndex ofTotalTileCount:(int)totalTileCount {
// float percentComplete = ((float)tileIndex / (float)totalTileCount) * 100;
//NSString *update = [NSString stringWithFormat:#"%.0f%%", percentComplete];
//NSLog(#"%#",update);
}
- (void)tileCacheDidFinishBackgroundCache:(RMTileCache *)tileCache {
NSLog(#"DONE!");
}
- (void)tileCacheDidCancelBackgroundCache:(RMTileCache *)tileCache {
NSLog(#"Canceled!");
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Is it stopping at the same tile count each time? Is there a map view visible at the same time? If you pause your app in the debugger, what is it doing once it seems to have stopped downloading tiles in the background?
I have found the following code and I need help with editing it. I am not really familiar with texture rendering.
First of all, init method takes a rect and magnifies only that area? How can I make it more dynamic and magnify only whatever is underneath the magnifying glass?
Secondly, Is it possible to change the shape to circle rather than rectangle?
Or Can I use an image as the frame of the magnifying glass?
Here is the code..
Cheers..
.h file
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface Magnify : CCNode {
BOOL active;
CGRect rect;
CGFloat magnifyScale;
CCNode *renderNode;
CCRenderTexture *renderTexture;
}
- (id)initWithNodeToMagnify:(CCNode *)n rect:(CGRect)rectToMagnify scale:(CGFloat)scale;
- (void)enable;
- (void)disable;
.m file
#import "Magnify.h"
#implementation Magnify
- (id)initWithNodeToMagnify:(CCNode *)n rect:(CGRect)rectToMagnify scale:(CGFloat)scale
{
if (self = [super init]) {
self.visible = active = NO;
renderNode = n;
rect = rectToMagnify;
magnifyScale = scale;
renderTexture = [[CCRenderTexture renderTextureWithWidth:rect.size.width height:rect.size.height] retain];
[self addChild:renderTexture];
}
return self;
}
- (void)enable
{
self.visible = active = YES;
[self scheduleUpdate];
}
- (void)disable
{
self.visible = active = NO;
[self unscheduleUpdate];
}
- (void)drawAreaToTexture
{
[renderTexture beginWithClear:0.0 g:0.0 b:0.0 a:1.0];
// shift the renderNode's position to capture exactly the rect we need
CGPoint originalPosition = renderNode.position;
renderNode.position = ccpSub(originalPosition, rect.origin);
// scale the node as we want
CGFloat originalScale = renderNode.scale;
renderNode.scale = magnifyScale;
[renderNode visit];
// shift renderNode's position back
renderNode.position = originalPosition;
// scale back
renderNode.scale = originalScale;
[renderTexture end];
}
- (void)update:(ccTime)dt
{
[self drawAreaToTexture];
}
- (void)dealloc
{
[renderTexture release];
[super dealloc];
}
#end
OK, so, as I mentioned above for something like this, one possible answer is to use the CCLens3D class to get the "effect" of magnifying something in a circular manner.
I found using this to be a little tricky because it doesn't seem to work unless it's a child of the top level node of your 'scene'.
Here is some code I use to create a lens that moves around the screen, and then disappears:
// Create the lens object first.
//
CCLens3D *lens =
[CCLens3D actionWithPosition:fromPos
radius:50
grid:ccg(50, 50)
duration:2.0];
// Set the "size" of the lens effect to suit your needs.
//
[lens setLensEffect:1.0];
// In my case, I then move the lens to a new position. To apply an action on
// a lens, you need to give the actions to the actionManager in the
// CCDirector instance.
//
CCMoveTo *move = [CCMoveTo actionWithDuration:2.0 position:toPos];
// I had another action in this array, but this will do.
//
CCSequence *seq = [CCSequence actions:move, nil];
// Now tell the actionManager to move the lens. This is odd, but it works.
//
[[[CCDirector sharedDirector] actionManager] addAction:seq target:lens paused:NO];
// Now just for some more weirdness, to actually make the lens appear and operate
// you run it as an action on the node it would normally be a child of. In my case
// 'self' is the CCLayer object that is the root of the current scene.
//
// Note that the first action is the lens itself, and the second is a special
// one that stops the lens (which is a "grid" object).
//
[self runAction:[CCSequence actions:lens, [CCStopGrid action], nil]];
I imagine that you should be able to stop the grid by running the CCStopGrid action when you want to. In my case it is a programmed thing. In yours it might be when the user lets go of a button.
I have got a UITabBarController in a Storyboard. Right now, it has got 5 UITabBarItems. When I am in the other UITabBarItem, I want to update the Badge on the other UITabBarItem(my "Downloads") just like the iTunes App does with this "jump-like" animation when you buy a song or album. Is this possible? If Yes, how?
Thank you.
Yes...
There is a lot to an animation like the I'll call it "send to Downloads" type animation. I'll answer this question using an example.
Warning: this example breaks the MVC paradigm more than I'd like, but it's long enough as it is.
I'll use a simple Storyboard like this (in fact, exactly this):
I'll start by describing the "First View Controller - First":
Those many buttons in the view are connected to the one listed IBAction method. And that's about all the description needed for that view controller. Here is its .m file:(truncated)
//#import "First_View_Controller.h"
#interface First_View_Controller ()
#property (weak, nonatomic) DownloadViewController *downloadViewController;
#end
#implementation First_View_Controller
#synthesize downloadViewController = _downloadViewController;
-(DownloadViewController *)downloadViewController{
if (!_downloadViewController){
// Code to find instance of DownloadViewController in the tabBarController's view controllers.
for (UIViewController *vc in self.tabBarController.viewControllers) {
if ([vc isKindOfClass:[DownloadViewController class]]){
_downloadViewController = (DownloadViewController *)vc;
break;
}
}
}
return _downloadViewController;
}
-(IBAction)buttonPush:(UIButton *)button{
[self.downloadViewController addADownload:nil withViewToAnimate:button];
}
// Other typical VC crap...
#end
The IBAction is fairly self-explanatory. It gets reference to the instance of DownloadViewController, by looking through the tabBarController's view controllers, and passes the view to animate to that instance.
Now for DownloadViewController.m. It's a lot of code. I've commented it, to try to make it clear:
#import "DownloadViewController.h"
#import <QuartzCore/QuartzCore.h>
// A Category on UITabBar to grab the view of a tab by index.
#implementation UITabBar (WhyIsntThisBuiltIn)
-(UIView *)nj_ViewOfTabNumber:(NSUInteger)number{
if (number == NSNotFound) return nil;
// Fairly standard method for getting tabs, getting the UIControl objects from the 'subviews' array.
// I pulled the next few lines from an SO question.
NSMutableArray *tabs = [[NSMutableArray alloc] init];
[self.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
if ([(NSObject *)obj isKindOfClass:UIControl.class]){
[tabs addObject:obj];
}
}];
// The code above gets the tabs' views, but they may not be in the correct order.
// This sort is required if a view controller has been replaced,...
// Since, in that case, the order in which the tabs' views appear in the 'subviews' array will not be the left-to-right order.
[tabs sortUsingComparator:^NSComparisonResult(UIView *obj1, UIView *obj2){
CGFloat v1 = obj1.center.x;
CGFloat v2 = obj2.center.x;
if (v1<v2) return NSOrderedAscending;
if (v1>v2) return NSOrderedDescending;
return NSOrderedSame;
}];
// This if is required for the case where the view controller is in the "more" tab.
if (number >= tabs.count) number = tabs.count-1;
return [tabs objectAtIndex:number];
}
#end
// A Category on UITabBarController to get the view of a tab that represents a certain view controller.
#implementation UITabBarController (WhyIsntThisBuiltIn)
-(UIView *)nj_viewOfTabForViewController:(UIViewController *)viewController{
// Find index of the passed in viewController.
NSUInteger indexOfViewController = [self.viewControllers indexOfObject:viewController];
if (indexOfViewController == NSNotFound) return nil;
// Return the view of the tab representing the passed in viewController.
return [self.tabBar nj_ViewOfTabNumber:indexOfViewController];
}
#end
// Insert required warning about using #defines here.
#define MY_ANIMATION_DURATION 0.8
#implementation DownloadViewController{
NSUInteger _numberOfDownloads;
}
-(void)updateBadgeValue{
self.tabBarItem.badgeValue = [NSString stringWithFormat:#"%i",_numberOfDownloads];
}
// This method creates a "snapshot" of the animation view and animates it to the "downloads" tab.
// Removal of the original animationView must, if desired, be done manually by the caller.
-(void)addADownload:(id)someDownload withViewToAnimate:(UIView *)animationView{
// update model...
_numberOfDownloads++;
// Animate if required
if (animationView){
// Create a `UIImageView` of the "animationView" name it `dummyImageView`
UIGraphicsBeginImageContextWithOptions(animationView.frame.size, NO, [[UIScreen mainScreen] scale]);
[animationView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *dummyImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *dummyImageView = [[UIImageView alloc] initWithImage:dummyImage];
dummyImageView.frame = animationView.frame;
// Determine UIView of tab using non-private API.
UITabBarController *tabBarController = self.tabBarController;
UIView *downloadsTab = [tabBarController nj_viewOfTabForViewController:self];
// Determine animation points in tabBarController's view's coordinates.
CGPoint animationStartPoint = [tabBarController.view convertPoint:dummyImageView.center fromView:dummyImageView.superview];
CGPoint animationEndPoint = [tabBarController.view convertPoint:downloadsTab.center fromView:downloadsTab.superview];
CGFloat totalXTravel = animationEndPoint.x - animationStartPoint.x;
// This is an arbitrary equation to create a control point, this is by no means canonical.
CGPoint controlPoint = CGPointMake(animationEndPoint.x, animationStartPoint.y - fabs(totalXTravel/1.2));
// Create the animation path.
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:animationStartPoint];
[path addQuadCurveToPoint:animationEndPoint controlPoint:controlPoint];
// Create the CAAnimation.
CAKeyframeAnimation *moveAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
moveAnimation.duration = MY_ANIMATION_DURATION;
moveAnimation.path = path.CGPath;
moveAnimation.removedOnCompletion = NO;
moveAnimation.fillMode = kCAFillModeBoth;
[tabBarController.view addSubview:dummyImageView];
dummyImageView.center = animationStartPoint;
// Animate the move.
[dummyImageView.layer addAnimation:moveAnimation forKey:#""];
// Use the block based API to add size reduction and handle completion.
[UIView animateWithDuration:MY_ANIMATION_DURATION
animations:^{
dummyImageView.transform = CGAffineTransformMakeScale(0.3, 0.3);
}
completion:^(BOOL b){
// Animate BIG FINISH! nah, just...
[dummyImageView removeFromSuperview];
[self updateBadgeValue];
}];
}
}
// Other typical VC crap...
#end
And that's about it. When run, this code produces a fairly strong jump from the buttons on the top left, but the buttons on the right, especially on the lower right, are sort-of tossed. And as the animation ends the badge on the downloads tab counts up. A pretty decent knock-off of the effect Apple uses when you purchase content on iTunes.
Remember to add the Quartz Framework to your app.
The iPad programming guide says that the splitView's left pane is fixed to 320 points. But 320 pixels for my master view controller is too much. I would like to reduce it and give more space to detail view controller. Is it possible by anyway?
Link to the document which speaks about fixed width.
If you subclass UISplitViewController, you can implement -viewDidLayoutSubviews and adjust the width there. This is clean, no hacks or private APIs, and works even with rotation.
- (void)viewDidLayoutSubviews
{
const CGFloat kMasterViewWidth = 240.0;
UIViewController *masterViewController = [self.viewControllers objectAtIndex:0];
UIViewController *detailViewController = [self.viewControllers objectAtIndex:1];
if (detailViewController.view.frame.origin.x > 0.0) {
// Adjust the width of the master view
CGRect masterViewFrame = masterViewController.view.frame;
CGFloat deltaX = masterViewFrame.size.width - kMasterViewWidth;
masterViewFrame.size.width -= deltaX;
masterViewController.view.frame = masterViewFrame;
// Adjust the width of the detail view
CGRect detailViewFrame = detailViewController.view.frame;
detailViewFrame.origin.x -= deltaX;
detailViewFrame.size.width += deltaX;
detailViewController.view.frame = detailViewFrame;
[masterViewController.view setNeedsLayout];
[detailViewController.view setNeedsLayout];
}
}
In IOS 8.0 you can easily do this by doing the following:
1. In your MasterSplitViewController.h add
#property(nonatomic, assign) CGFloat maximumPrimaryColumnWidth NS_AVAILABLE_IOS(8_0);
2. In your MasterSplitViewController.m viewDidLoad method add
self.maximumPrimaryColumnWidth = 100;
self.splitViewController.maximumPrimaryColumnWidth = self.maximumPrimaryColumnWidth;
This is a really good, simple and easy feature of IOS 8.
this code is work for me
[splitViewController setValue:[NSNumber numberWithFloat:200.0] forKey:#"_masterColumnWidth"];
No.
There are two private properties
#property(access,nonatomic) CGFloat masterColumnWidth;
#property(access,nonatomic) CGFloat leftColumnWidth; // both are the same!
but being private mean they can't be used for AppStore apps.
iOS 8 introduced a new property:
// An animatable property that can be used to adjust the maximum absolute width of the primary view controller in the split view controller.
#property(nonatomic, assign) CGFloat maximumPrimaryColumnWidth NS_AVAILABLE_IOS(8_0); // default: UISplitViewControllerAutomaticDimension
Use this property to adjust your master viewcontroller to your desired width.
Here is how I did this in iOS8 with Swift.
class MainSplitViewController: UISplitViewController {
override func viewDidLoad() {
self.preferredDisplayMode = UISplitViewControllerDisplayMode.AllVisible
self.maximumPrimaryColumnWidth = 100 // specify your width here
}
}
If you need to change the width dynamically from within your master/detail view in the split view, then do something like this:
var splitViewController = self.splitViewController as MainSplitViewController
splitViewController.maximumPrimaryColumnWidth = 400
The storyboard way would be this one, mentioned by #Tim:
Furthermore, if you want the Master view to always take up a certain percentage of the screen then you can use the Key Path = "preferredPrimaryColumnWidthFraction" instead and set the value to 0.2 (for 20% screen size).
Please note that the "maximumPrimaryColumnWidth" is set to 320, so if you try the screen percent value of 0.5 (50%) it won't go above 320. You can add a key path for maximumPrimaryColumnWidth if you need to override this.
None of the answers worked for me on iOS7, so I did some of my own research and created a working solution. This will involve subclassing UISplitViewController for the full functionality.
I will present the answer as if we just created a new project for iPad with all device orientations and have set the custom UISplitViewController as the main view controller.
Create your custom UISplitViewController. In this example mine is called MySplitViewController. All code will be based in MySplitViewController.m.
We're going to need to access a method from the UISplitViewControllerDelegate so add that and set the delegate. We'll also setup a delegate forwarder incase you need to call the delegate methods from another class.
#interface MySplitViewController () <UISplitViewControllerDelegate>
#property (nonatomic, weak) id<UISplitViewControllerDelegate> realDelegate;
#end
#implementation MySplitViewController
- (instancetype)init {
self = [super init];
if (self) {
self.delegate = self;
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
self.delegate = self;
}
return self;
}
- (void)setDelegate:(id<UISplitViewControllerDelegate>)delegate {
[super setDelegate:nil];
self.realDelegate = (delegate != self) ? delegate : nil;
[super setDelegate:delegate ? self : nil];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
id delegate = self.realDelegate;
return [super respondsToSelector:aSelector] || [delegate respondsToSelector:aSelector];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
id delegate = self.realDelegate;
return [delegate respondsToSelector:aSelector] ? delegate : [super forwardingTargetForSelector:aSelector];
}
Setup the master and detail view controllers.
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController* masterViewController = [[UIViewController alloc] init];
masterViewController.view.backgroundColor = [UIColor yellowColor];
UIViewController* detailViewController = [[UIViewController alloc] init];
detailViewController.view.backgroundColor = [UIColor cyanColor];
self.viewControllers = #[masterViewController, detailViewController];
}
Lets add our desired width to a method for easy reference.
- (CGFloat)desiredWidth {
return 200.0f;
}
We'll manipulate the master view controller before presenting it.
- (void)splitViewController:(UISplitViewController *)svc popoverController:(UIPopoverController *)pc willPresentViewController:(UIViewController *)aViewController {
id realDelegate = self.realDelegate;
if ([realDelegate respondsToSelector:#selector(splitViewController:popoverController:willPresentViewController:)]) {
[realDelegate splitViewController:svc popoverController:pc willPresentViewController:aViewController];
}
CGRect rect = aViewController.view.frame;
rect.size.width = [self desiredWidth];
aViewController.view.frame = rect;
aViewController.view.superview.clipsToBounds = NO;
}
However, now we're left with a display like this.
So were going to override a private method. Yes a private method, it will still be acceptable in the App Store since its not an underscore private method.
- (CGFloat)leftColumnWidth {
return [self desiredWidth];
}
This deals with portrait mode. So a similar thing for -splitViewController:willShowViewController:invalidatingBarButtonItem: and you should be set for landscape.
However none of this will be needed in iOS8. You'll be able to simply call a min and max width property!
use the following code before assigning to the rootviewcontroller. It works for me with ios7
[self.splitViewController setValue:[NSNumber numberWithFloat:256.0] forKey:#"_masterColumnWidth"];
self.window.rootViewController = self.splitViewController;
Since no one mentioned that this can be done from IB, I want to add this answer. Apparently, you can set "User Defined Runtime Attributes" for the UISplitViewContorller with following details:
Key Path:masterColumnWidth
Type: Number
Value: 250
In my case, I had to set both maximum and minimum to make this work
mySplitViewController.preferredDisplayMode = .allVisible;
mySplitViewController.maximumPrimaryColumnWidth = UIScreen.main.bounds.width/2;
mySplitViewController.minimumPrimaryColumnWidth = UIScreen.main.bounds.width/2;
You can use GSSplitViewController. This one will work on iOS 7 and 8
splitView = [[GSSplitViewController alloc] init];
splitView.masterPaneWidth = 180;
You can also include it by adding pod 'GSSplitViewController' to your Podfile.
ViewController.h
#property(nonatomic, assign) CGFloat maximumPrimaryColumnWidth NS_AVAILABLE_IOS(8_0);
ViewController.m
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
if (SYSTEM_VERSION_LESS_THAN(#"10.0")) {
[self setValue:[NSNumber numberWithFloat:200.0]forKey:#"_masterColumnWidth"];
}else{
self.maximumPrimaryColumnWidth = 200;
self.splitViewController.maximumPrimaryColumnWidth = self.maximumPrimaryColumnWidth;
}
Swift 3.0 you use like
let widthfraction = 2.0 //Your desired value for me 2.0
splitViewController?.preferredPrimaryColumnWidthFraction = 0.40
let minimumWidth = min((splitViewController?.view.bounds.size.width)!,(splitViewController?.view.bounds.height)!)
splitViewController?.minimumPrimaryColumnWidth = minimumWidth / widthFraction
splitViewController?.maximumPrimaryColumnWidth = minimumWidth / widthFraction
let leftNavController = splitViewController?.viewControllers.first as! UINavigationController
leftNavController.view.frame = CGRect(x: leftNavController.view.frame.origin.x, y: leftNavController.view.frame.origin.y, width: (minimumWidth / widthFraction), height: leftNavController.view.frame.height)
// in UISplitViewController subclass
// let more space for detail in portrait mode
- (void)viewWillLayoutSubviews
{
CGFloat width;
if (UIInterfaceOrientationIsPortrait(UIApplication.sharedApplication.statusBarOrientation)){
width = CGRectGetWidth(self.view.bounds) * 0.25f;
}
else {
width = CGRectGetWidth(self.view.bounds) * 0.33f;
}
width = (NSInteger)fminf(260, fmaxf(120, width));
self.minimumPrimaryColumnWidth = width;
self.maximumPrimaryColumnWidth = width;
[super viewWillLayoutSubviews];
}
This code work for me:)
#interface UISplitViewController(myExt)
- (void)setNewMasterSize:(float)size;
#end
#implementation UISplitViewController(myExt)
- (void)setNewMasterSize:(float)size
{
_masterColumnWidth = size;
}
#end
and use it on each operation with view (like rotation)