So i need to write a QR reader for an iphone application. Usually frameworks like ZXING and ZBAr allow you to download an application and then read the barcode. I want to integrate it within the application itself. So basically i want to be able to tap a button and have it reading the QR code. Is this possible? If so are there any proper documentation on this? I tried using ZXing but when i link binaries libZXingWidget.a is unavailable. Also the documentation is not enough to know how to integrate it within the application. So let me know.
Here's code to setup ZBar, make sure to add the SDK to your project and link your library.
-
(void)viewDidLoad
{
[ZBarReaderView class];
readerView.readerDelegate = self;
readerView.tracksSymbols = NO;
//CHOOSE CAMERA
if (some setting isEqual to CameraRear) {
readerView.device = [self backFacingCameraIfAvailable];
}
else {
readerView.device = [self frontFacingCameraIfAvailable];
}
[self relocateReaderPopover:[self interfaceOrientation]];
[readerView start];
}
-(AVCaptureDevice *)frontFacingCameraIfAvailable
{
NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
AVCaptureDevice *captureDevice = nil;
for (AVCaptureDevice *device in videoDevices)
{
if (device.position == AVCaptureDevicePositionFront)
{
captureDevice = device;
break;
}
}
// couldn't find one on the front, so just get the default video device.
if ( ! captureDevice)
{
captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
}
return captureDevice;
}
-(AVCaptureDevice *)backFacingCameraIfAvailable
{
NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
AVCaptureDevice *captureDevice = nil;
for (AVCaptureDevice *device in videoDevices)
{
if (device.position == AVCaptureDevicePositionBack)
{
captureDevice = device;
break;
}
}
// couldn't find one on the front, so just get the default video device.
if ( ! captureDevice)
{
captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
}
return captureDevice;
}
-(void)relocateReaderPopover:(UIInterfaceOrientation)toInterfaceOrientation{
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
readerView.previewTransform = CGAffineTransformMakeRotation(M_PI_2);
} else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
readerView.previewTransform = CGAffineTransformMakeRotation(-M_PI_2);
} else if (toInterfaceOrientation== UIInterfaceOrientationPortraitUpsideDown) {
readerView.previewTransform = CGAffineTransformMakeRotation(M_PI);
} else {
readerView.previewTransform = CGAffineTransformIdentity;
}
}
This is a step-by-step tutorial on how to add an integrated QR code reader to your iphone app. Look at this example ZXing project if you need to know how to use it.
I have managed to integrate ZXING into my test project. I have done this a little time ago so I might not remember all the problems that I faced and solved.
Into my workspace folder I have copied ZXing-2.0 folder. Inside I have only left folders: cpp, docs and iphone. To my workspace (containing the test project) I have added ZXingWidget project from the folder ZXing-2.0. This allowed me to add to linking setting libZXingWidget.a.
When I build my test project, XCode detects dependency and builds the widget first and then builds the test project and links it against libZXingWidget.a.
Here's a simple view controller that I have implemented to display inside camera view able to detect QR code.
//
// MyVC.m
//
//
#import "MyVC.h"
#import "QRCodeReader.h"
#interface MyVC () {
ZXingWidgetController *_widController;
}
#end
#implementation MyVC
#synthesize labelResultString;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
_widController = [[ZXingWidgetController alloc] initWithDelegate:(id<ZXingDelegate>)self showCancel:YES OneDMode:NO];
QRCodeReader* qrcodeReader = [[QRCodeReader alloc] init];
NSSet *readers = [[NSSet alloc ] initWithObjects:qrcodeReader,nil];
//[qrcodeReader release];
_widController.readers = readers;
//[readers release];
//NSBundle *mainBundle = [NSBundle mainBundle];
//_widController.soundToPlay = [NSURL fileURLWithPath:[mainBundle pathForResource:#"beep-beep" ofType:#"aiff"] isDirectory:NO];
_widController.overlayView.displayedMessage = #"";
_widController.wantsFullScreenLayout = NO;
//[self presentModalViewController:_widController animated:NO];
_widController.view.frame = CGRectMake(10, 10, 300, 300);//self.view.frame;
_widController.view.autoresizingMask = UIViewAutoresizingNone;
_widController.overlayView.frame = CGRectMake(0, 0, 300, 300);
_widController.overlayView.cropRect = CGRectMake(20, 20, 260, 260);
[self.view addSubview:_widController.view];
//[_widController release];
}
- (void)viewDidUnload {
[super viewDidUnload];
self.labelResultString = nil;
}
- (void)dealloc {
self.labelResultString = nil;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[_widController viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[_widController viewDidAppear:animated];
[[UIApplication sharedApplication] setStatusBarHidden:NO];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[_widController viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[_widController viewDidDisappear:animated];
}
#pragma mark - ZXingDelegateMethods
- (void)zxingController:(ZXingWidgetController*)controller didScanResult:(NSString *)result {
self.labelResultString.text = result;
}
- (void)zxingControllerDidCancel:(ZXingWidgetController *)controller {
[self dismissModalViewControllerAnimated:YES];
}
#end
Once you have ZXing set up correctly the code to get a QR reader is literally this easy:
Make sure to import these two guys for QR:
#import "ZXingWidgetController.h"
#import "QRCodeReader.h"
Then in your controller you will set up the code reader as follows:
ZXingWidgetController *widController = [[ZXingWidgetController alloc] initWithDelegate:self showCancel:YES OneDMode:NO];
QRCodeReader *qrcodeReader = [[QRCodeReader alloc] init];
NSSet *readers = [[NSSet alloc] initWithObjects:qrcodeReader,nil];
widController.readers = readers;
[self presentModalViewController:widController animated:YES];
You basically set up the controller that handles the code reading (ZXingWidgetController) and then give it a set of all the types of code readers you want (here I just used QRCodeReader.) Lastly, you just present it as a modal view controller.
Then you will need to implement the <ZXingDelegate> and the following delegate functions:
- (void)zxingController:(ZXingWidgetController*)controller didScanResult:(NSString *)result
{
[self dismissModalViewControllerAnimated:YES];
NSLog(#"%#",result); //Simple NSString result.
}
- (void)zxingControllerDidCancel:(ZXingWidgetController*)controller
{
[self dismissModalViewControllerAnimated:YES];
NSLog(#"Cancelled");
}
The hardest part is just setting it up. I found this tutorial to be the most helpful (Sorry that I am using a link): How to install ZXing in Xcode 4 The comments are also helpful if you have any problems.
Related
Recently I have come across a problem, where my app goes into background during initial launch, but this only happens on iPhone 4S with iOS6. I have tested on:
simulator, with different hardware/software configurations
iPhone 5 (iOS 6)
iPhone 4S (iOS 5.1)
iPad2 (iOS 6)
and it is working on all of them, but on the iPhone 4S with iOS6 the launch of the app takes about 20s before going into background, if you "re-launch" the app after a couple of seconds you see that it is still running and works without any problem.
Is there any know issue with iPhone4S (iOS6) that causes this or there is something special about this model? [I already tested it on different iPhone4s (iOS6) and it is happening on all of them]
EDIT
I noticed something weird will doing some more testing, iPhone 4s (iOS6) is the only one that doesn't show the loading screen (first view controller), it only shows the launch image.. anyway, here is the code for the AppDelegate and the first view controller:
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Optional: automatically track uncaught exceptions with Google Analytics.
[GAI sharedInstance].trackUncaughtExceptions = YES;
// Optional: set Google Analytics dispatch interval to e.g. 20 seconds.
[GAI sharedInstance].dispatchInterval = 20;
// Optional: set debug to YES for extra debugging information.
[GAI sharedInstance].debug = NO;
// Create tracker instance.
__unused id<GAITracker> tracker = [[GAI sharedInstance] trackerWithTrackingId:#"UA-APP-ID"];
return YES;
}
EcraPrincipalViewController.m
#import "EcraPrincipalViewController.h"
#import "ListaPercursosTableViewController.h"
#import "GlobalVars.h"
#import "AppDelegate.h"
#interface EcraPrincipalViewController ()
#end
#implementation EcraPrincipalViewController
{
int progresso;
int sizePercursos;
}
#synthesize lbl_versao;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.trackedViewName = #"iPhone - EcrãPrincipal";
// Do any additional setup after loading the view.
NSMutableArray *views = [[NSMutableArray alloc] initWithArray:[self.navigationController viewControllers]];
UIStoryboard *story = [UIStoryboard storyboardWithName:#"WalkMeStoryBoard" bundle:nil];
ListaPercursosTableViewController *listaV = [story instantiateViewControllerWithIdentifier:#"view_lista"];
[views replaceObjectAtIndex:0 withObject:listaV];
[self.navigationController setViewControllers:views];
lbl_info.text = NSLocalizedString(#"downloading", NULL);
lbl_versao.text = NSLocalizedString(#"walkme_versao", NULL);
dispatch_queue_t queue = dispatch_queue_create("com.WalkMe.downloadPercursos", NULL);
dispatch_async(queue, ^{
[[GlobalVars Instance] getLevadas:self];
});
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (BOOL)shouldAutorotate
{
return [[GlobalVars Instance] podeRodar];
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)novaAtualizacao
{
NSLog(#"Atualização encontrada");
lbl_info.text = NSLocalizedString(#"atualizacao_encontrada", NULL);
}
-(void)atualizarPercursosInternos
{
NSLog(#"VERIFICAÇAO Interna");
lbl_info.text = NSLocalizedString(#"a_atualizar_percursos", NULL);
}
-(void)setNewDownload:(NSNumber*)size
{
NSLog(#"Iniciou VERIFICAÇAO");
progresso = 0;
sizePercursos = [size intValue];
lbl_info.text = [NSString stringWithFormat:#"%# 0/%d", NSLocalizedString(#"novos_percursos", NULL), sizePercursos];
}
-(void)setProgress
{
NSLog(#"Progresso");
progresso++;
lbl_info.text = [NSString stringWithFormat:#"%# %d/%d", NSLocalizedString(#"novos_percursos", NULL), progresso, sizePercursos];
}
-(void)goToLista
{
NSLog(#"ACABOU VERIFICAÇAO");
UIStoryboard *story = [UIStoryboard storyboardWithName:#"WalkMeStoryBoard" bundle:nil];
[[GlobalVars Instance] getLevadas];
[self.navigationController pushViewController:[story instantiateViewControllerWithIdentifier:#"view_lista"] animated:NO];
}
- (void)viewDidUnload {
[self setLbl_versao:nil];
[super viewDidUnload];
}
#end
Thank you very much for your attention and help :)
While trying to figure out what was happening, the suggestions made in the comments to the question made me realize that for some reason the app was calling the same method twice and for some reason that cause the app to go to the background.
To solve the problem, simply remove this lines from viewDidLoad
NSMutableArray *views = [[NSMutableArray alloc] initWithArray:[self.navigationController viewControllers]];
UIStoryboard *story = [UIStoryboard storyboardWithName:#"WalkMeStoryBoard" bundle:nil];
ListaPercursosTableViewController *listaV = [story instantiateViewControllerWithIdentifier:#"view_lista"];
[views replaceObjectAtIndex:0 withObject:listaV];
[self.navigationController setViewControllers:views];
and add them to goToLista (this is called after the initial loading is complete).
I ended up with:
- (void)viewDidLoad
{
[super viewDidLoad];
self.trackedViewName = #"iPhone - EcrãPrincipal";
// Do any additional setup after loading the view.
lbl_info.text = NSLocalizedString(#"downloading", NULL);
lbl_versao.text = NSLocalizedString(#"walkme_versao", NULL);
dispatch_queue_t queue = dispatch_queue_create("com.WalkMe.downloadPercursos", NULL);
dispatch_async(queue, ^{
[[GlobalVars Instance] getLevadas:self];
});
}
-(void)goToLista
{
NSLog(#"ACABOU VERIFICAÇAO");
[[GlobalVars Instance] getLevadas];
NSMutableArray *views = [[NSMutableArray alloc] initWithArray:[self.navigationController viewControllers]];
UIStoryboard *story = [UIStoryboard storyboardWithName:#"WalkMeStoryBoard" bundle:nil];
ListaPercursosTableViewController *listaV = [story instantiateViewControllerWithIdentifier:#"view_lista"];
[views replaceObjectAtIndex:0 withObject:listaV];
[self.navigationController setViewControllers:views];
[self.navigationController pushViewController:[story instantiateViewControllerWithIdentifier:#"view_lista"] animated:NO];
}
This solves the problem, but I don't know why. If anyone knows please do tell, because I really would like to know.
Thank you :)
i have a problem in getting the GADInterstitial custom ads i have tried this code
if(nil != m_pBannerView)
{
m_pBannerView.delegate = nil;
[m_pBannerView release];
m_pBannerView = nil;
}
m_pBannerView = [[GADBannerView alloc] initWithAdSize:kGADAdSizeBanner];
m_pBannerView.delegate = self;
m_pBannerView.rootViewController = self;
m_pBannerView.adUnitID = #"AdMob Publisher ID";
m_pBannerView.rootViewController = self;
[self.view addSubview:m_pBannerView];
GADRequest *request = [GADRequest request];
request.testing = YES;
[m_pBannerView loadRequest:request];
if(nil != m_pInterstitial)
{
[m_pInterstitial release];
m_pInterstitial = nil;
}
m_pInterstitial = [[GADInterstitial alloc] init];
m_pInterstitial.delegate = self;
m_pInterstitial.adUnitID = #"INTERSTITIAL_AD_UNIT_ID";
GADRequest *interstialRequest = [GADRequest request];
interstialRequest.testing = YES;
[m_pInterstitial loadRequest: interstialRequest];
}
And in GADInterstitial Delegates i am calling [ad presentFromRootViewController:self];
but still i am not able to get the custom ads please help me.
You have to use your own unique id for adUnitID property
GADInterstitial is an interesting way to show ads in your Apps, and kind of a tricky one too. Following this example, lets pursue the following steps:
First we need to set up the environment for them to show at all.
Download the GoogleAdMobAdsSdkiOS, preferably the latest. Add
the SDK to your project, but do remember to delete the example
Projects in the AddOns folder in the SDK.
Next, add the following frameworks in your Project>>Build
Phases>>Link Binary With Libraries:
AdSupport.framework (select Optional if catering for < iOS7)
StoreKit.framework
CoreData.framework
CoreAudio.framework
AVFoundation.framework
MessageUI.framework
AudioTool.framework
libGoogleAdMobAds.a (placed in the SDK folder)
The basics are complete. Now we need to select the ViewController we wish to see our Ads in. So here's some code, here we import the header for GADInterstitialDelegate and extend it with our MainViewController.h:
#import "GADInterstitialDelegate.h"
#define kSampleAdUnitID #"/6253334/dfp_example_ad/interstitial"
#class GADInterstitial;
#class GADRequest;
#interface MainViewController : UIViewController<GADInterstitialDelegate>
{
BOOL isLoaded_;
NSTimer *quickie_;
}
#property(nonatomic, strong) GADInterstitial *interstitial;
//Make sure the delegate is handled properly.
Now we need to move to the implementation i.e. in MainViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
// FOLLOW THE CODE BELOW FOR ADMOD INTERESTIAL IMPLEMENTATION
[self initializeAds];
quickie_ = [[NSTimer alloc] init];
quickie_ = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(showAdd) userInfo:nil repeats:YES];
}
-(void)showAdd
{
if(isLoaded_ && [quickie_ isValid])
{
[quickie_ invalidate];
quickie_ = nil;
[self.interstitial presentFromRootViewController:self];
}
}
- (GADRequest *)request
{
GADRequest *request = [GADRequest request];
return request;
}
-(void)initializeAds
{
// Create a new GADInterstitial each time. A GADInterstitial will only show one request in its
// lifetime. The property will release the old one and set the new one.
self.interstitial = [[GADInterstitial alloc] init];
self.interstitial.delegate = self;
// Note: kSampleAdUnitId is a staticApId for example purposes. For personal Ads update kSampleAdUnitId with your interstitial ad unit id.
self.interstitial.adUnitID = kSampleAdUnitID;
[self.interstitial loadRequest:[self request]];
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
self.loadingSpinner.center = CGPointMake(CGRectGetWidth(self.view.bounds) / 2, self.loadingSpinner.center.y);
}
- (void)interstitialDidReceiveAd:(GADInterstitial *)interstitial
{
isLoaded_ = YES;
}
- (void)interstitial:(GADInterstitial *)interstitial didFailToReceiveAdWithError:(GADRequestError *)error
{
isLoaded_ = NO;
}
//----ADS IMPLEMENTATION TILL HERE--->//
The timer "quickie_" here constantly checks if the Ad has successfully loaded and when it does, it shoots the Ad on the ViewController if the user still is on it. The static kSampleAdUnitID is a sampleId that always works. Thats it. Run your code and find your Interstitial Ad on your ViewController of choice.
Hope I helped. Cheers! :)
I did the "Build and analyze" in xCode and get "Dereference of null pointer" . I noted in my code below for which row I get the message. I'm developing for iPhone.
"PDFScrollView.h"
#import "ENsightAppDelegate.h"
#interface PDFScrollView : UIScrollView <UIScrollViewDelegate> {
ENsightAppDelegate *appDelegate;
}
#end
"PDFScrollView.m"
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
// Set up the UIScrollView
self.showsVerticalScrollIndicator = NO;
self.showsHorizontalScrollIndicator = NO;
self.bouncesZoom = YES;
//self.decelerationRate = UIScrollViewDecelerationRateFast;
self.delegate = self;
[self setBackgroundColor:[UIColor grayColor]];
self.maximumZoomScale = 5;//200
self.minimumZoomScale = 0.5;//.85
self.userInteractionEnabled = YES;
self.delaysContentTouches = YES;
self.canCancelContentTouches = NO;
}
appDelegate =(ENsightAppDelegate *) [[UIApplication sharedApplication] delegate];
pdfView=[appDelegate GetLeaveView];
[self addSubview:pdfView];
return self;
}
It's not the complete code, pasted what I think is useful.
I find this quite strange. How come I get this message?
If self is nil, then you can't access instance variables of 'self'. So you should reference those variables only inside of the if statement. In other words, only if self is not nil.
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Set up the UIScrollView
self.showsVerticalScrollIndicator = NO;
// ... bunch of other properties set...
// ... btw, better style here would be to set the ivars directly (in an initializer)
appDelegate =(ENsightAppDelegate *) [[UIApplication sharedApplication] delegate];
pdfView=[appDelegate GetLeaveView];
[self addSubview:pdfView];
}
return self;
}
is appDelegate an instance var of the class? if so, you should be doing it in the if (self =..... same with the [self addSubview...]. basically if that if statement fails, you don't have an object to work with.
appDelegate is actually [self appDelegate], so if you assign null to self, you dereference null pointer.
Anyway - self points to the current object, why would you assign to it? You can use it directly without an assignment.
And last thing - you do use self explicitly in the on-before-last line anyway, and here it is clear that it is null:
[self addSubview:pdfView];
Update
I swapped out completely different code for pushViewController, and it is still crashing... seems like pushViewController is not the culprit. Here is what I added instead:
NSString *videoURL = [[NSString alloc] initWithFormat:#"http://www.vimeo.com/m/#/%#", videoID];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:videoURL]];
It opens up the URL in Safari, and then crashes.. wtf?
PushViewController crashes with no error in the console, but I do get an EXC_BAD_ACCESS error in Xcode. The crash doesn't happen until after the view controller has been pushed... but the view its pushing is empty... no code to mess up.
My code is below:
MainViewController.m
PlayVimeo *playTest = [[PlayVimeo alloc] initWithNibName:#"PlayVimeo" bundle:nil];
//playTest.videoID = videoID;
[self.navigationController pushViewController:playTest animated:YES];
[playTest release];
PlayVimeo.m
#import "PlayVimeo.h"
#import "SVProgressHUD.h"
#implementation PlayVimeo
#synthesize videoID, wView;
-(void)viewDidLoad {
[super viewDidLoad];
//Show loading alert
[SVProgressHUD showInView:self.view status:#"Loading Video..."];
}
-(void)viewWillAppear:(BOOL)animated {
NSLog(#"Play View Loaded!");
[self vimeoVideo];
}
-(void)vimeoVideo {
NSLog(#"Video ID: %#", videoID);
NSString *html = [NSString stringWithFormat:#"<html>"
#"<head>"
#"<meta name = \"viewport\" content =\"initial-scale = 1.0, user-scalable = no, width = 460\"/></head>"
#"<frameset border=\"0\">"
#"<frame src=\"http://player.vimeo.com/video/%#?title=0&byline=0&portrait=1&autoplay=1\" width=\"460\" height=\"320\" frameborder=\"0\"></frame>"
#"</frameset>"
#"</html>",
videoID];
NSLog(#"HTML String: %#", html);
[wView loadHTMLString:html baseURL:[NSURL URLWithString:#""]];
//Dismiss loading alert
[SVProgressHUD dismissWithSuccess:#"Playing..."];
}
- (void)viewDidUnload {
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
-(void)dealloc {
[super dealloc];
}
Navigation Controller Code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
[Appirater appLaunched];
return YES;
}
Console on crash:
sharedlibrary apply-load-rules all
Current language: auto; currently objective-c
(gdb)
It's likely that the culprit is
[playTest release];
Without seeing the rest of your code, I would still say that you likely need to release this after you're done with the video.
The code can not be fixed, it seems. With the UIWebView class reference, there is an example program TransWeb. Take this as base, it has a window and a navigation controller with a webview in it (in the xib). In MyViewController it reads a html-file and displays it. What you need to do is to change the main view to landscape and replace the html-code with yours. Avoid the frame-stuff.
I am adding multiple AVPlayer objects in a scrollview. For the first time this is working fine but as i go back to my previous view and come back again i am not able to see AVPlayer objects in scrollView.
I need as matrix of multiple AVPlayer to show thumbnails of my videos.
Please help me asap
Thanks in advance.
I had a similar problem and after a lot of searching discovered that the AVPLayerItem object causes this issue.
Basically when the AVPlayer items go offscreen you need to make sure everything is released properly, and then recreate everything when they come back on screen.
As part of the release process, include the line:
[AVPlayer replaceCurrentItemWithPlayerItem:nil];
That sorted a very similar issue for me.
[AVPlayer replaceCurrentItemWithPlayerItem:nil];
also solves my problem of cannot releasing current player item immediately. (can't manually do that because it's retained by the class..)
Here's how you get 10 AVPlayers to play simultaneously, inside the same scrollView, each and every time you scroll it:
//
// ViewController.m
// VideoWall
//
// Created by James Alan Bush on 6/13/16.
// Copyright © 2016 James Alan Bush. All rights reserved.
//
#import "ViewController.h"
#import "AppDelegate.h"
static NSString *kCellIdentifier = #"Cell Identifier";
#interface ViewController () {
dispatch_queue_t dispatchQueueLocal;
}
#end
#implementation ViewController
- (id)initWithCollectionViewLayout:(UICollectionViewFlowLayout *)layout
{
if (self = [super initWithCollectionViewLayout:layout])
{
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:kCellIdentifier];
dispatchQueueLocal = dispatch_queue_create( "local session queue", DISPATCH_QUEUE_CONCURRENT );
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.collectionView setDataSource:self];
[self.collectionView setContentSize:CGSizeMake(AppDelegate.sharedAppDelegate.width, AppDelegate.sharedAppDelegate.height)];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)dealloc {
[super dealloc];
}
#pragma mark <UICollectionViewDataSource>
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return AppDelegate.sharedAppDelegate.assetsFetchResults.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = (UICollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier forIndexPath:indexPath];
[CATransaction begin];
[CATransaction setCompletionBlock:^{
cell.contentView.layer.sublayers = nil;
dispatch_release(dispatchQueueLocal);
}];
dispatch_retain(dispatchQueueLocal);
dispatch_async( dispatchQueueLocal, ^{
[self drawPlayerLayerForCell:cell atIndexPath:indexPath];
});
[CATransaction commit];
return cell;
}
- (void)drawPlayerLayerForCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
void (^drawPlayerLayer)(UICollectionViewCell*, NSIndexPath*) = ^(UICollectionViewCell* cell, NSIndexPath* indexPath) {
[AppDelegate.sharedAppDelegate.imageManager requestPlayerItemForVideo:AppDelegate.sharedAppDelegate.assetsFetchResults[indexPath.item] options:nil resultHandler:^(AVPlayerItem * _Nullable playerItem, NSDictionary * _Nullable info) {
dispatch_async(dispatch_get_main_queue(), ^{
if(![[info objectForKey:PHImageResultIsInCloudKey] boolValue]) {
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:[AVPlayer playerWithPlayerItem:playerItem]];
[playerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[playerLayer setBorderColor:[UIColor whiteColor].CGColor];
[playerLayer setBorderWidth:1.0f];
[playerLayer setFrame:cell.contentView.bounds];
[cell.contentView.layer addSublayer:playerLayer];
[(AVPlayer *)playerLayer.player play];
} else {
[AppDelegate.sharedAppDelegate.imageManager requestImageForAsset:AppDelegate.sharedAppDelegate.assetsFetchResults[indexPath.item]
targetSize:CGSizeMake(AppDelegate.sharedAppDelegate.flowLayout.itemSize.width, AppDelegate.sharedAppDelegate.flowLayout.itemSize.height)
contentMode:PHImageContentModeAspectFill
options:nil
resultHandler:^(UIImage *result, NSDictionary *info) {
dispatch_async(dispatch_get_main_queue(), ^{
cell.contentView.layer.contents = (__bridge id)result.CGImage;
});
}];
}
});
}];
};
drawPlayerLayer(cell, indexPath);
}
#end
Here's the AppDelegate implementation file:
//
// AppDelegate.m
// VideoWall
//
// Created by James Alan Bush on 6/13/16.
// Copyright © 2016 James Alan Bush. All rights reserved.
//
#import "AppDelegate.h"
#import "ViewController.h"
#implementation AppDelegate
+ (AppDelegate *)sharedAppDelegate
{
return (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch
self.width = [[UIScreen mainScreen] bounds].size.width / 2.0;
self.height = [[UIScreen mainScreen] bounds].size.height / 4.0;
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = [self viewController];
self.window.rootViewController.view = self.viewController.view;
[self.window makeKeyAndVisible];
return YES;
}
- (ViewController *)viewController {
ViewController *c = self->_viewController;
if (!c) {
c = [[ViewController alloc] initWithCollectionViewLayout:[self flowLayout]];
[c.view setFrame:[[UIScreen mainScreen] bounds]];
self->_viewController = c;
}
return c;
}
- (UICollectionViewFlowLayout *)flowLayout {
UICollectionViewFlowLayout *v = self->_flowLayout;
if (!v) {
v = [UICollectionViewFlowLayout new];
[v setItemSize:CGSizeMake(AppDelegate.sharedAppDelegate.width, AppDelegate.sharedAppDelegate.height)];
[v setSectionInset:UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0)];
[v setMinimumLineSpacing:0.0];
[v setMinimumInteritemSpacing:0.0];
[v setEstimatedItemSize:CGSizeMake(AppDelegate.sharedAppDelegate.width, AppDelegate.sharedAppDelegate.height)];
self->_flowLayout = v;
}
return v;
}
- (PHCachingImageManager *)imageManager {
PHCachingImageManager *i = self->_imageManager;
if (!i) {
i = [[PHCachingImageManager alloc] init];
self->_imageManager = i;
}
return i;
}
- (PHFetchResult *)assetsFetchResults {
PHFetchResult *i = self->_assetsFetchResults;
if (!i) {
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumVideos options:nil];
PHAssetCollection *collection = smartAlbums.firstObject;
if (![collection isKindOfClass:[PHAssetCollection class]])
return nil;
PHFetchOptions *allPhotosOptions = [[PHFetchOptions alloc] init];
allPhotosOptions.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:NO]];
i = [PHAsset fetchAssetsInAssetCollection:collection options:allPhotosOptions];
self->_assetsFetchResults = i;
}
return i;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#end
These are the only two files you need; no NIB/XIB.