been having problems with this since iOS 6. I can't really figure out whats changed that would cause this behavior. This used to work perfectly in 5. Now the activity indicator doesn't come up timely or at all. Any help would be much appreciated.
-(void)myMethod
{
UIView *currentTitleView = [[self navigationItem] titleView];
// Create an activity indicator and start it spinning in the nav bar
UIActivityIndicatorView *aiview = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[[self navigationItem] setTitleView:aiview];
[aiview startAnimating];
// Start of Block code
void (^block)(arg1, arg2) =
^(arg1, arg2)
{
block code;
[aiview stopAnimating];
[[self navigationItem] setTitleView:currentTitleView];
};
// End of Block code
}
It sounds like myMethod is being called from a background thread. As a general rule, all interactions with UIKit elements (which includes UIActivityIndicatorView) should always be done on the main thread. Try using GCD to move the UI code to the main queue (a.k.a. main thread).
-(void)myMethod {
dispatch_async(dispatch_get_main_queue(), ^{
UIView *currentTitleView = [[self navigationItem] titleView];
// Create an activity indicator and start it spinning in the nav bar
UIActivityIndicatorView *aiview = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[[self navigationItem] setTitleView:aiview];
[aiview startAnimating];
});
// Start of Block code
void (^block)(arg1, arg2) =
^(arg1, arg2)
{
block code;
dispatch_async(dispatch_get_main_queue(), ^{
[aiview stopAnimating];
[[self navigationItem] setTitleView:currentTitleView];
});
};
// End of Block code
}
Related
DemoViewController is responsible for showing tutorial to the user. Contains animations and timer to repeat gesture demo while ignored by the user. Is instantiated form DataViewController. Is nil-ed, but later respawns on its internal timer. I need it to be completely gone so it is not created again when user returns to the first page.
dataViewController.h
#import "DemoViewController.h"
#property (strong,nonatomic) DemoViewController *demoController;
dataViewController.h
-(void) viewWillAppear:(BOOL)animated {
// demoPageNumber is 0
if ((self.demoController== nil) && ([_pageNumber isEqualToNumber:demoPageNumber])){
self.demoController = [[DemoViewController alloc] initWithView:self.view];
}
}
-(void) viewWillDisappear:(BOOL)animated{
[self.demoController free]; // invalidate timer, nil all internal objects
self.demoController=nil; // should free object
}
DemoViewController.m
-(void) free{
[animationRespawnTimer invalidate];
animationRespawnTimer=nil;
}
-(void) respawnDemoWithSelector:(SEL)selector{
NSLog(#"Timer fired %#", self);
[self resetTimer];
animationRespawnTimer = [NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:selector
userInfo:nil
repeats:NO];
}
-(void) showScrollDemo{
NSLog(#"showScrollDemo fired");
[self stopPreviousAnimations];
scrollHandView.frame = CGRectMake(315.0, 700.0, 100, 100);
scrollHandView.hidden=NO;
scrollHandView.alpha=1;
[UIView animateWithDuration:3.0
delay: 0.0
options: (UIViewAnimationOptionCurveEaseOut |
UIViewAnimationOptionRepeat )
animations:^{
[UIView setAnimationRepeatCount:3];
scrollHandView.frame = CGRectMake(315.0, 300.0, 100, 100);
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
scrollHandView.alpha=0;
}
completion:^(BOOL finished){
scrollHandView.hidden=YES;
[self respawnDemoWithSelector: #selector(showScrollDemo)];
}
];
}
];
}
When the page is loaded and if this is the first page, demoController is instantiated, and on exit from the page nil-ed after clean-up (custom free method). According to my understanding this should delete the demoController object with all its contents including the timer. And debug area shows exactly that! Until when on the new page the demoController timer mystically respawns from nowhere with previous object ID.
17:59:14.041 viewWillAppear begin (self.demoController null)
18:00:05.346 viewWillAppear, <DemoViewController: 0x7580310> //demoController created
18:00:15.786 in the demoController method the "showScrollDemo" is fired
18:00:19.834 viewWillAppear end <DemoViewController: 0x7580310>
Page was loaded, demo performed fine. Now I'm flipping the page. viewWillDisappear event is fired.
18:01:17.966 viewWillDisappear begin, send "free" message to demoController
18:01:17.966 "free" was performed from <DemoViewController: 0x7580310>
18:01:34.059 viewWillDisappear end (self.demoController null)
So, the "self.demoController" is null. Then the demoController respawns itself with previous ID
18:02:36.514 Timer fired <DemoViewController: 0x7580310>
Why? Timer can not respawn, it is set to repeats:NO.
I assume that it is the completion block of the animation that calls respawnDemoWithSelector and creates a new timer.
According to this answer: https://stackoverflow.com/a/9676508/1187415, you can stop all running
animations with
[self.view.layer removeAllAnimations];
Alternatively, you can add a boolean property done to the DemoViewController which is set
to YES in the free method, and checked in the completion block of the animation:
if (!self.done)
[self respawnDemoWithSelector: #selector(showScrollDemo)];
UPDATE: The animation blocks capture a strong reference to self, thus preventing
the object from being deallocated. The standard solution to that "retain-cycle" problem
(assuming that you use ARC) is to use a weak reference to self. That would look like this:
__weak typeof(self) weakSelf = self;
[UIView animateWithDuration:3.0
delay: 0.0
options: (UIViewAnimationOptionCurveEaseOut |
UIViewAnimationOptionRepeat )
animations:^{
[UIView setAnimationRepeatCount:3];
weakSelf.scrollHandView.frame = CGRectMake(315.0, 300.0, 100, 100);
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
weakSelf.scrollHandView.alpha=0;
}
completion:^(BOOL finished){
weakSelf.scrollHandView.hidden=YES;
[weakSelf respawnDemoWithSelector: #selector(showScrollDemo)];
}
];
}
];
weakSelf does not hold a strong reference to the DemoViewController and is set to nil
automatically if the object it points to is deallocated. In that case, all message sent to weakSelf inside the blocks do just nothing.
I am having a hell of a time with AVQueuePlayer. Very simply, I am making it on an array of AVPlayerItems built with playerItemWithURL: and pointing to video assets on a server out on the Net.
If I try to run this thing in the stimulator (sic) it plays through the first asset, begins playing the second and just dies. Sometimes it doesn't even get through the first asset.
Now, I understand that the sim behaves differently than the device, so I tried it on the device as well, with the same problem.
When I log the various status changes on the item, I see that the buffer runs out, and the player just dies. This seems a bit silly to me for a construct that is playing a queue of items... of course the buffer may underrun, but why would it just die?
Am I doing something wrong, or is this how it is supposed to behave?
I have gotten around this problem by observing the loadedTimeRanges property and when it changes sending PLAY to the player. But this results in stuttering playback, which sucks too.
Here is my code. Can someone please tell me how to do this without the playback being complete crap?
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handlePlayerItemReachedEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.queuePlayer];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handlePlayerStalled:) name:AVPlayerItemPlaybackStalledNotification object:self.queuePlayer];
NSString *baseURL = #"http://www.aDomain.com/assets/";
NSMutableArray *vidItemArray = [[NSMutableArray alloc] initWithCapacity:5];
for (int i = 1; i <= 5; i++) {
AVPlayerItem *vid = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:[baseURL stringByAppendingFormat:#"%d.mov", i]]];
[vid addObserver:self forKeyPath:#"status" options:0 context:nil];
[vid addObserver:self forKeyPath:#"playbackBufferEmpty" options:0 context:nil];
[vid addObserver:self forKeyPath:#"loadedTimeRanges" options:0 context:nil];
[vidItemArray addObject:vid];
}
self.queuePlayer = [AVQueuePlayer queuePlayerWithItems:vidItemArray];
[self.mPlaybackView setPlayer:self.queuePlayer];
}
- (void)handlePlayerItemReachedEnd:(NSNotification *)notification {
NSLog(#"Clip #%d finished playing", [self.queuePlayer.items indexOfObject:self.queuePlayer.currentItem]);
}
- (void)handlePlayerStalled:(NSNotification *)notification {
NSLog(#"CLIP STALLED...");
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([object isKindOfClass:[AVPlayerItem class]]) {
AVPlayerItem *item = (AVPlayerItem *)object;
if ([keyPath isEqualToString:#"status"]) {
switch (item.status) {
case AVPlayerItemStatusFailed:
NSLog(#"player item status failed");
break;
case AVPlayerItemStatusReadyToPlay:
NSLog(#"player item status is ready to play");
[self.queuePlayer play];
break;
case AVPlayerItemStatusUnknown:
NSLog(#"player item status is unknown");
break;
}
}
else if ([keyPath isEqualToString:#"playbackBufferEmpty"]) {
if (item.playbackBufferEmpty) {
NSLog(#"player item playback buffer is empty");
}
}
else if ([keyPath isEqualToString:#"loadedTimeRanges"]) {
NSLog(#"Loaded time range = %#", item.loadedTimeRanges);
self.queuePlayer play];
}
}
}
You should not add many AVPlayerItem to AVQueuePlayer initially because they will begin buffering at the same time, at discretion of the framework.
Attempt to load one file and then on KVO currentItem changed queue the next one, it might solve your problem. I'm using this to achieve gapless playback.
First, apologies if the answer is quite obvious but I'm brand new to iOS development (like this is the first app I'm trying to program even though it's just for me to play with :P) so the probability that my problem is rather minor is quite high.
Code (zip file containing the xcode project):
http://www.mediafire.com/?p55xw0q2dwwwwvm
I promise there's nothing else in there that's harmful :).
Problem:
I'm following:
http://www.techotopia.com/index.php/An_iPhone_iOS_6_Storyboard-based_Collection_View_Tutorial
I'm attempting to make a UICollectionView to play around with. I got down to the "Testing the Application" section. I put my own spin on a few parts from the guide:
instead of using different pictures, I just use 1 picture with a label at the bottom that just has a number in it. The number is dynamically set. Yes, I could implement it without the array I'm using, but having that array there will be helpful if I extend this project's scope in the future.
I added code in viewDidLoad to set the view layout to a flow layout. This was done again for futureproofing since once I get this working at a basic form I want to play around with formatting which will require me to subclass flowlayout.
The code compiles without errors but nothing shows up on the screen. I've checked the code to the best of my ability for about an hour or two, but nothing I did made any difference. My controller class is a bare bones attempt to just get the collection view to show up on screen, and my cell class is a bare bones cell with just an imageview and label in it.
Thanks for any help that can be given to get this working!
tl;dr
Just look at the bolded stuff. Code provided below for convenience:
MyCollectionViewController.m
#import "MyCollectionViewController.h"
#interface MyCollectionViewController ()
#property (nonatomic, strong) NSMutableArray *dataArray;
#end
#implementation MyCollectionViewController
#synthesize dataArray = _dataArray;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Create data to display
for(int i = 0; i < 50; i++){
[self.dataArray addObject:[NSString stringWithFormat:#"%d", i]];
}
// Configure Layout
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[self.collectionView setCollectionViewLayout:flowLayout];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark -
#pragma mark UICollectionViewDataSource
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
-(NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.dataArray count];
}
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCollectionViewCell *myCell = [collectionView dequeueReusableCellWithReuseIdentifier:#"MyCell" forIndexPath:indexPath];
int row = [indexPath row];
UIImage* image = [UIImage imageNamed:#"200px-AND_ANSI"];
myCell.cellTitle = [self.dataArray objectAtIndex:row];
myCell.cellImageView.image = image;
myCell.backgroundColor = UIColor.whiteColor;
return myCell;
}
MyCollectionViewcontroller.h
#import <UIKit/UIKit.h>
#import "MyCollectionViewCell.h"
#interface MyCollectionViewController : UICollectionViewController <UICollectionViewDataSource, UICollectionViewDelegate>
#end
MyCollectionViewCell.h
#import <UIKit/UIKit.h>
#interface MyCollectionViewCell : UICollectionViewCell
#property (strong, nonatomic) IBOutlet UIImageView *cellImageView;
#property (strong, nonatomic) IBOutlet UILabel *cellTitle;
#end
MyCollectionViewCell.m
#import "MyCollectionViewCell.h"
#implementation MyCollectionViewCell
#synthesize cellImageView = _cellimageView;
#synthesize cellTitle = _cellTitle;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
self.dataArray is nowhere allocated/initialized in your code and therefore is equal to nil. Sending messages to nil is allowed, but has no effect, therefore even after
for(int i = 0; i < 50; i++){
[self.dataArray addObject:[NSString stringWithFormat:#"%d", i]];
}
self.dataArray is still nil, and [self.dataArray count] returns 0.
You have to allocate and initialize the array with
self.dataArray = [[NSMutableArray alloc] init];
A proper place is some initXXX method of the view controller. But initWithNibName: is not called if the view controller is instantiated from a storyboard file, you have to use initWithCoder: instead:
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
self.dataArray = [NSMutableArray array];
}
return self;
}
I have been struggling with something for weeks and it has brought a real halt to my progress. I have asked a question a few times on SO, people have been helpful but no-one has cracked what I am doing wrong. It seems a fairly simple thing so hopefully someone out there will have a lightbulb moment and solve this. I am implementing a TWRequest, the result is coming back in a dictionary, I am looping through the results to extract a part of the tweet and creating an array of these 'text' components. Straight adter the loop through I am peinting the log of the array - _twitterText and it prints fine. Staright after this method is complete it seems as though _twitterText is being dumped. I have created it in my .h file as a strong property and created an ivar in viewdidload. Still no joy. how do I retain this array to use in another Method?
Here is my .h file....
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import "CustomCell.h"
#import "AppDelegate.h"
#import <Twitter/Twitter.h>
#interface MyViewController : UITableViewController <CLLocationManagerDelegate>
{
CLLocationManager *here;
}
#property(strong) NSDictionary *dict;
#property(strong) CLLocationManager *here;
#property (strong, nonatomic) NSMutableArray *twitterText;
- (void)fetchTweets;
#end </p>
Here is my .m implementation file......
#import "MyViewController.h"
#interface MyViewController ()
#end
#implementation MyViewController
#synthesize dict;
#synthesize twitterText = _twitterText;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_twitterText = [[NSMutableArray alloc] init];
here = [[CLLocationManager alloc] init];
here.delegate = self;
[here startUpdatingLocation];
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
NSLog(#"phrase carried over is %#", delegate.a);
[self fetchTweets];
}
- (void)fetchTweets
{
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:
#"http://search.twitter.com/search.json?q=%40wimbledon"]
parameters:nil requestMethod:TWRequestMethodGET];
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
if ([urlResponse statusCode] == 200)
{
// The response from Twitter is in JSON format
// Move the response into a dictionary and print
NSError *error;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
//NSLog(#"Twitter response: %#", dict);
NSArray *results = [dict objectForKey:#"results"];
//Loop through the results
for (NSDictionary *tweet in results) {
// Get the tweet
NSString *twittext = [tweet objectForKey:#"text"];
// Save the tweet to the twitterText array
[_twitterText addObject:twittext];
}
NSLog(#"MY ************************TWITTERTEXT************** %#", _twitterText);
}
else
NSLog(#"Twitter error, HTTP response: %i", [urlResponse statusCode]);
}];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
#warning Potentially incomplete method implementation.
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
#warning Incomplete method implementation.
// Return the number of rows in the section.
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
//cell.venueDetails.text = [_twitterText objectAtIndex:indexPath.row];
NSLog(#"MY ************************OTHER BIT THAT WONT PRINT************** %#", _twitterText);
return cell;
}
So the issue here is that your completion handler that you pass to -[TWTweet performRequestWithHandler:] isn't going to (can't) fire until the network connection is complete and the server responds to your request. That could take hundreds of milliseconds or even seconds to complete. (Or it may never happen).
Meanwhile while that is happening, the UITableView wants to draw itself and so will ask you how many sections/rows you have and then will ask you for a cell for each row. So when the table view asks, you should return the actual number of rows you have to draw at that time:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.twitterText count]; // the actual number of rows we have right now
}
So then the next step you need is to reload the table when your data is actually in from the server. That will prompt your table view to ask again for the number of sections and rows, and then ask for cells for each section and row. So somewhere in your completion block after you've processed all your data you will need to do this:
dispatch_async(dispatch_get_main_queue(), ^{
// you'll need an outlet to the UITableView
// here I assume you call that 'tableView'
// then just ask it to reload on the main thread
[self.tableView reloadData];
});
I hope that helps?
[EDIT] I am changing this to more concisely explain what my problem was, after having more precisely pinpointed the issue.
I am working on core data for my app, but am stumped. It hangs in this method every time. No crashes, no logs, no nothing. It just hangs.
- (void)insertNewObject:(id)sender
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
Section *newSection = (Section *)newObject;
newSection.title = #"inserted";
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
I discovered that if I put NSLogs in these two delegate methods:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
they just keep getting called infinite number of times.
Ok, I figured it out. I was creating an infinite loop.
This delegate method gets called:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
Then this eventually gets called because I called [self.tableView beginUpdates]; in the delegate method.
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Section *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
object.title = [NSString stringWithFormat:#"Chapter %i", indexPath.row];
cell.textLabel.text = object.title;
}
Then this delegate method:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
The the problem is that I was actually changing the NSManagedObject's attributes while it was updating the content
object.title = [NSString stringWithFormat:#"Chapter %i", indexPath.row];
this caused controllerWillChangeContent: to be called once again creating a loop that just goes round and round.