I'm attempting to make my iOS6.0 app back compatible with 5.1. I've turned off the obvious things (e.g. autolayout) but am getting stuck at a strange stage.
My app takes data from an XML source and puts it in a core date structure. On iOS 6 this works perfectly. On iOS 5 it gets stuck here
else if (self.dataStorage.documentState == UIDocumentStateClosed) {
NSLog(#"THIS FIRES = db on disk but closed");
[self.dataStorage openWithCompletionHandler:^(BOOL success) {
NSLog(#"THIS NEVER FIRES");
}];
}
If I look at self.datastorage it is what I would expect (a closed managed document) fileURL: file://localhost/ ..... /Library/Application%20Support/iPhone%20Simulator/5.1/Applications/E3E9192D-2DFE-4882-9041-00A1DF9E98D6/Documents/Default%20Database documentState: [Closed]
Edit: Actually works fine with iOS 5.0 or 6.0+. My problem is purely with iOS 5.1 run on the iPhone simulator. Could this just be a bug with the simulator? It will not open a closed UIManagedDocument nor create an non-existing file.
Here is the full code for completeness:
- (void)setDataStorage:(UIManagedDocument *)database
{
if (_dataStorage != database) {
_dataStorage = database;
[self useDocument];
}
}
-(UIManagedDocument*) initialiseDatabase {
if (!self.dataStorage) {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"DefaultDatabase"];
self.dataStorage = [[UIManagedDocument alloc] initWithFileURL:url]; // setter will create this for us on disk
}
return self.dataStorage;
}
- (void)useDocument {
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.dataStorage.fileURL path]]) {
// does not exist on disk, so create it
NSLog(#"db not on disk");
[self.dataStorage saveToURL:self.dataStorage.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
NSLog(#"Doesn't fire");
}];
} else if (self.dataStorage.documentState == UIDocumentStateClosed) {
NSLog(#"db on disk but closed");
// exists on disk, but we need to open it
[self.dataStorage openWithCompletionHandler:^(BOOL success) {
NSLog(#"Doesn't fire");
}];
} else if (self.dataStorage.documentState == UIDocumentStateNormal) {
NSLog(#"db on disk and open");
}
}
Thanks
Now I've identified the problem in a little more detail it appears that many people have asked this question before.
Sadly, there has never been a satisfactory solution. However, it is only an issue/bug with the simulator and shouldn't be a problem for real devices (as confirmed by me testing on a 5.1 iPad).
Related
I've read that we can now play custom sounds on the apple watch in watchos 3.
According to the announcement from Apple so apparently there is but I don't have an example to test it out: 3D spatial audio implemented using SCNAudioSource or SCNAudioPlayer. Instead, use playAudioSource:waitForCompletion: or the WatchKit sound or haptic APIs. Found here: https://developer.apple.com/library/prerelease/content/releasenotes/General/WhatsNewInwatchOS/Articles/watchOS3.html
Can someone place a simple example of this. I'm not using SceneKit in my app as I don't need it but if that's the only way to play a custom sound then I'd like to know the minimum code required to accomplish this. Preferably in Objective c but I'll take it in whatever shape. I'm ok using SpriteKit if that's easier also.
Here's what I have so far but it doesn't work:
SCNNode * audioNode = [[SCNNode alloc] init];
SCNAudioSource * audioSource = [SCNAudioSource audioSourceNamed:#"mysound.mp3"];
SCNAudioPlayer * audioPlayer = [SCNAudioPlayer audioPlayerWithSource:audioSource];
[audioNode addAudioPlayer:audioPlayer];
SCNAction * play = [SCNAction playAudioSource:audioSource waitForCompletion:YES];
[audioNode runAction:play];
I can confirm, that #ApperleyA solution really works!
Here is the swift version:
var _audioPlayer : AVAudioPlayerNode!
var _audioEngine : AVAudioEngine!
func playAudio()
{
if (_audioPlayer==nil) {
_audioPlayer = AVAudioPlayerNode()
_audioEngine = AVAudioEngine()
_audioEngine.attach(_audioPlayer)
let stereoFormat = AVAudioFormat(standardFormatWithSampleRate: 44100, channels: 2)
_audioEngine.connect(_audioPlayer, to: _audioEngine.mainMixerNode, format: stereoFormat)
do {
if !_audioEngine.isRunning {
try _audioEngine.start()
}
} catch {}
}
if let path = Bundle.main.path(forResource: "test", ofType: "mp3") {
let fileUrl = URL(fileURLWithPath: path)
do {
let asset = try AVAudioFile(forReading: fileUrl)
_audioPlayer.scheduleFile(asset, at: nil, completionHandler: nil)
_audioPlayer.play()
} catch {
print ("asset error")
}
}
}
This is Objective-c but can be translated into Swift
I ended up using AVAudioEngine and AVAudioPlayerNode to play audio on the Apple watch.
The gist of how to do this is as follows:
I call the following inside the init method of my AudioPlayer (it's an NSObject subclass to encapsulate the functionality)
_audioPlayer = [[AVAudioPlayerNode alloc] init];
_audioEngine = [[AVAudioEngine alloc] init];
[_audioEngine attachNode:_audioPlayer];
AVAudioFormat *stereoFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100 channels:2];
[_audioEngine connect:_audioPlayer to:_audioEngine.mainMixerNode format:stereoFormat];
if (!_audioEngine.isRunning) {
NSError* error;
[_audioEngine startAndReturnError:&error];
}
I have a cache setup so I don't recreate the AVAudioFile assets every time I want to play a sound but you don't need to.
So next create an AVAudioFile object:
NSError *error;
NSBundle* appBundle = [NSBundle mainBundle];
NSURL *url = [NSURL fileURLWithPath:[appBundle pathForResource:key ofType:#"aifc"]];
AVAudioFile *asset = [[AVAudioFile alloc] initForReading:url &error];
Then play that file:
[_audioPlayer scheduleFile:asset atTime:nil completionHandler:nil];
[_audioPlayer play];
UPDATE: If the app goes to sleep or is put to the background there is a chance the audio will stop playing/fade out. By activating an Audio Session this will be prevented.
NSError *error;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
if (error) {
NSLog(#"AVAudioSession setCategory ERROR: %#", error.localizedDescription);
}
[[AVAudioSession sharedInstance] setActive:YES error:&error];
if (error) {
NSLog(#"AVAudioSession setActive ERROR: %#", error.localizedDescription);
}
I didn't go over handling any errors but this should work. Don't forget to #import <AVFoundation/AVFoundation.h> at the top of your implementation file.
This worked for me in the simulator
let soundPath = Bundle.main.path(forResource: "cheerMP3", ofType: "mp3")
let soundPathURL = URL(fileURLWithPath: soundPath!)
let audioFile = WKAudioFileAsset(url: soundPathURL)
let audioItem = WKAudioFilePlayerItem(asset: audioFile)
let audioPlayer = WKAudioFilePlayer.init(playerItem: audioItem)
if audioPlayer.status == .readyToPlay
{
audioPlayer.play()
}
else
{
print("Not ready!!")
}
but only if I had a breakpoint at both audioPlayer.play() and after the last }.
dunqan, what did you put at the top of the file, the import statements? I wasn't able to include
import AVFoundation
without an error using Xcode 8.2.1
During the call I try to switch voice from internal speaker to Loud speaker on iOS device using pjsip 2.2 library. It returns TRUE as success, but physically it doesn't change sound destination.
I use the next code
- (BOOL)setLoud:(BOOL)loud {
if (loud) {
#try {
pjmedia_aud_dev_route route = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
pj_status_t pj_status = pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
&route, PJ_TRUE);
if (pj_status == PJ_SUCCESS) {
return YES;
}
else
{
return NO;
}
}
#catch (NSException *exception) {
return NO;
}
} else {
#try {
pjmedia_aud_dev_route route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
pj_status_t pj_status = pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
&route, PJ_TRUE);
if (pj_status == PJ_SUCCESS) {
return YES;
}
else
{
return NO;
}
}
#catch (NSException *exception) {
return NO;
}
}
}
Could you suggest how can we make this work?
With the introduction of iOS 7, you should now be using AVAudioSession to handle any audio management. It took me a long time to finally get this to work but I finally figured out the problem of why my audio was not automatically routing to my iPhone Speaker. The problem is that when you answer a call, pjsip was automatically overriding the AVAudioSessionPortOverride I was performing before the call is answered. To tackle this problem, you simply just have to override the output audio port AFTER answering the call.
To make my VoIP application work efficiently with the background mode, I decided to handle the audio routing in a custom callback method named on_call_state. This method, on_call_state, is called by pjsip when a call state has changed. As you can read here, http://www.pjsip.org/pjsip/docs/html/group__PJSIP__INV.htm, there are many different flags you can check for when a call state has changed. The states I used in this example are PJSIP_INV_STATE_CONNECTING and PJSIP_INV_STATE_DISCONNECTED.
PJSIP_INV_STATE_CONNECTING is called when a audio call connects to another peer.
PJSIP_INV_STATE_DISCONNECTED is called when a audio call ends with another peer.
static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
{
pjsua_call_info ci;
PJ_UNUSED_ARG(e);
pjsua_call_get_info(call_id, &ci);
PJ_LOG(3,(THIS_FILE, "Call %d state=%.*s", call_id,
(int)ci.state_text.slen,
ci.state_text.ptr));
if (ci.state == PJSIP_INV_STATE_CONNECTING) {
BOOL success;
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
withOptions:AVAudioSessionCategoryOptionMixWithOthers
error:&error];
if (!success) NSLog(#"AVAudioSession error setCategory: %#", [error localizedDescription]);
success = [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
if (!success) NSLog(#"AVAudioSession error overrideOutputAudioPort: %#", [error localizedDescription]);
success = [session setActive:YES error:&error];
if (!success) NSLog(#"AVAudioSession error setActive: %#", [error localizedDescription]);
} else if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
BOOL success;
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
success = [session setActive:NO error:&error];
if (!success) NSLog(#"AVAudioSession error setActive: %#", [error localizedDescription]);
}
}
I am using AVAssetWriter/AVAssetReader to transcode a PCM audio file to AAC. I have boiled it down to a simple project that works in iOS6 and fails in iOS7.
Every thing is going well until I get to [self.assetWriter finishWritingWithCompletionHandler:] Then the assetWriter goes into the failed state with the error set to -11800 AVFoundation unknown error with an internal error set to -12733 which apparently corresponds to SampleBufferNotReady.
dispatch_queue_t queue = dispatch_queue_create("audio.encode", DISPATCH_QUEUE_SERIAL);
success = [self.assetWriter startWriting];
if (!success)
{
[self showStatus:#"Export: writer failed to startWriting"];
return;
}
[self.assetWriter startSessionAtSourceTime:kCMTimeZero];
[assetWriterInput requestMediaDataWhenReadyOnQueue:queue
usingBlock:
^{
while([assetWriterInput isReadyForMoreMediaData])
{
NSAssert (self.assetWriter.status == AVAssetWriterStatusWriting, nil);
CMSampleBufferRef sampleBuffer = [assetReaderOutput copyNextSampleBuffer];
if (sampleBuffer)
{
NSAssert (CMSampleBufferIsValid(sampleBuffer), nil);
NSAssert (CMSampleBufferDataIsReady(sampleBuffer), nil);
BOOL success = [assetWriterInput appendSampleBuffer:sampleBuffer];
if (!success)
{
[self showError:self.assetWriter.error];
self.assetWriter = nil;
CFRelease(sampleBuffer);
return;
}
CFRelease(sampleBuffer);
}
else
{
if ([assetReader status] == AVAssetReaderStatusCompleted)
{
[assetWriterInput markAsFinished];
[self.assetWriter finishWritingWithCompletionHandler:^{
BOOL success = self.assetWriter.status == AVAssetWriterStatusCompleted;
if (success)
{
[self showStatus: #"Did it!"];
self.assetWriter = nil;
}
else
{
[self showError:self.assetWriter.error];
self.assetWriter = nil;
}
}];
}
else
{
[self showError:assetReader.error];
self.assetWriter = nil;
}
}
}
}
];
Note: I have posted a bug with Apple, posted to the dev forum and used a TSI. Haven't got any answers yet. My hope is one of you geniuses will point me to a workaround.
i have same problem with you, but finally i solve that problem, i use this method :
CMTime cmTime = CMTimeMake(longDuration, 1);
[assetWriter endSessionAtSourceTime:cmTime];
[assetWriter finishWritingWithCompletionHandler^(){
NSLog (#"finished writing");
];
usually we don't need call this if we call finishWritingWithCompletionHandler;
i hope this solve your problem.
I have an app where I present VC1 to pick a game and VC2 to submit plays for the selected game. When a user segues back from VC2 to VC1 I want to retain the game data for the game they were playing. Since it's iOS 6.0, I am using UIManagedDocument to access Core Data for storing and retrieving the game data. And I am totally stumped by the problem I am facing and after spending countless hours, I am reaching out to the wise folks on this forum.
When I run the code below in the Simulator, everything works fine, the data gets stored and I am also able to retrieve it and show it if the user picks the same game as earlier on to play. Unfortunately on the device, I can see that the data gets stored on segue - I put a breakpoint and looked at the persistentStore using iExplorer - but as soon as I go back to VC2 selecting the stored game, the persistentStore seems to be overwritten or purged of all data. In the debugger I noticed that the _persistentStores NSArray property of the _persistentStoreCoordinator object for "UIManagedDocument" always shows 0 when retrieval is done on the device.
Any help is much appreciated!!!
- (void) addOrGetDataToGamesDatabase:(CNPUIManagedDocument *)document withFlag:(BOOL)getFlag {
if (getFlag) {
NSLog(#"INSIDE addDataToGamesDatabase to get data");
}
else
NSLog(#"INSIDE addDataToGamesDatabase to set data");
dispatch_queue_t update = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
dispatch_sync(update, ^{
[document.managedObjectContext performBlockAndWait:^{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Game"];
request.predicate = [NSPredicate predicateWithFormat:#"game = %#", self.selectedGameID];
NSError *error = nil;
NSLog(#"Persistent Store Name = %#", [[document class] persistentStoreName]);
NSArray *matches = [document.managedObjectContext executeFetchRequest:request error:&error];
NSLog(#"Fetch Error if any: %# %#", error.debugDescription, [error userInfo]);
if (!matches || matches.count >1) {
NSLog(#"There is a problem with creating the game data");
}
//This is where it fails on the device as the match.count is always 0 as the fetch retrieves nothing
else if ([matches count] == 0) {
if (!getFlag) {
// Code to initialize the game data in store
}
}
else if ([matches count] == 1) {
// Another fetch - nested
if (!oTeammatches || [oTeammatches count] >1) {
NSLog(#"There is a problem with creating the offense team data");
}
else if ([oTeammatches count] == 0) {
if (!getFlag) {
//Code to initialize the team data in store
}
}
else if ([oTeammatches count] == 1) {
OTeam *newOTeam = [oTeammatches lastObject];
if (getFlag) {
//Retrieves data
//Shown by log lines beginning with "Getting"
}
else {
//Sets/Saves data
//Shown by log lines beginning with "Setting"
}
}
}
}];
if (!getFlag) {
[document updateChangeCount:UIDocumentChangeDone];
}
});
}
- (IBAction)pressBackButton:(UIButton *)sender {
[self addOrGetDataToGamesDatabase:self.gamesDatabase withFlag:NO];
if ((self.gamesDatabase.documentState & UIDocumentStateEditingDisabled) != UIDocumentStateEditingDisabled) {
[self.gamesDatabase saveToURL:self.gamesDatabase.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
if (success) {
NSLog(#"DB save file path: %#", self.gamesDatabase.fileURL);
NSLog(#"Saved!!!");
}
else
NSLog(#"Unable to save");
}];
}
[self performSegueWithIdentifier:#"BackToPickGame" sender:self];
}
- (void)viewDidLoad
{
[super viewDidLoad];
//Some Initialization code
if (!self.gamesDatabase) {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"GameDB"];
self.gamesDatabase = [[CNPUIManagedDocument alloc] initWithFileURL:url];
NSLog(#"DB File URL generated: %#", url);
NSLog(#"self.gameDatabase initialized");
}
[self addOrGetDataToGamesDatabase:self.gamesDatabase withFlag:YES];
NSLog(#"Calling game with ID %# , self.selectedGameID);
}
Some log info on the persistent store
DEVICE
First get on entering VC2
Printing description of document->_persistentStoreCoordinator:
Printing description of document->_persistentStoreCoordinator->persistentStores:
<_NSArrayM 0x1fd28ce0>(
)
First set in VC2 on exiting VC2
Printing description of document->_persistentStoreCoordinator:
Printing description of document->_persistentStoreCoordinator->persistentStores:
<_NSArrayM 0x1fd28ce0>(
(URL: file://localhost/var/mobile/Applications/4DD2D219-5AC1-406F-8020-260B01E46E0C/Documents/GameDB/StoreContent/persistentStore)
)
Second get on entering VC2
Printing description of document->_persistentStoreCoordinator:
Printing description of document->_persistentStoreCoordinator->persistentStores:
<_NSArrayM 0x211d4660>(
)
SIMULATOR
First get on entering VC2
Printing description of document->_persistentStoreCoordinator:
Printing description of document->_persistentStoreCoordinator->persistentStores:
<_NSArrayM 0x84e4b60>(
(URL: file://localhost/Users/Rujul/Library/Application%20Support/iPhone%20Simulator/6.0/Applications/B187169B-8D32-4BB1-AB41-33DB76637D9C/Documents/GameDB/StoreContent/persistentStore)
)
First set on exiting VC2
Printing description of document->_persistentStoreCoordinator:
Printing description of document->_persistentStoreCoordinator->persistentStores:
<_NSArrayM 0x84e4b60>(
(URL: file://localhost/Users/Rujul/Library/Application%20Support/iPhone%20Simulator/6.0/Applications/B187169B-8D32-4BB1-AB41-33DB76637D9C/Documents/GameDB/StoreContent/persistentStore)
)
Second get on entering VC2
Printing description of document->_persistentStoreCoordinator:
Printing description of document->_persistentStoreCoordinator->persistentStores:
<_NSArrayM 0xf777910>(
(URL: file://localhost/Users/Rujul/Library/Application%20Support/iPhone%20Simulator/6.0/Applications/B187169B-8D32-4BB1-AB41-33DB76637D9C/Documents/GameDB/StoreContent/persistentStore)
)
Check all the overwrites in your code (like above in UIDocumentSaveForOverwriting). Log the exact values before and after. I am sure you are going to find the culprit in this way.
Also, check what you are doing in each prepareForSegue:.
BTW, I would recommend to perhaps refactor the confusing addOrGetDataToGamesDatabase business into two easily understood methods.
I have a GameCenter Sandbox-Account have tested my game, earned achievements, etc.
Now I've made some changes and want to test earning Achievements again!
Do I have to make an entire new Sandbox-Account or is there a way to reset my account?
The following code is from the Apple Documentation.
- (void) resetAchievements
{
// Clear all locally saved achievement objects.
achievementsDictionary = [[NSMutableDictionary alloc] init];
// Clear all progress saved on Game Center
[GKAchievement resetAchievementsWithCompletionHandler:^(NSError *error)
{
if (error != nil)
// handle errors
}];
}
Also have a look at Apple's sample project GKTapper.
// Reset all the achievements for local player
- (void)resetAchievements
{
[GKAchievement resetAchievementsWithCompletionHandler: ^(NSError *error)
{
if (!error) {
[storedAchievements release];
storedAchievements = [[NSMutableDictionary alloc] init];
// overwrite any previously stored file
[self writeStoredAchievements];
} else {
// Error clearing achievements.
}
}];
}