IOS Developer Notes: Sharing Experience, Part 1
Hello, dear readers of Habr! I develop applications for iOS and Mac OS. I’ve been engaged in freelance for about a year now and, moving from client to client, I began to notice that I understand the problem once; and when a similar order appears, I simply use the modules already developed previously. In a series of articles "Notes iOS developer" I will try to highlight some aspects that are often found in orders; I’ll write a kind of cheat sheet, having read which, you can quickly and painlessly introduce new technology into your project. My notes in no way pretend to have a deep understanding of the processes, but describe an easy way to complete an order on time.
Content:
- Part 1: Work with Files; Singleton Template; Work with Audio; Work with Video; In-App Purchases
- Part 2: Own Popups; How to use Modal Segue in Navigation Controller; Core Graphics Work with UIWebView and ScrollView
- Part 3: Life without Autolayout; Splash Screen Work with device orientation in iOS 6+; UITextField Content Shift
- Part 4: Google Analytics; Push notifications PSPDFKit; Login to the application via Facebook; Tell your friends - Facebook, Twitter, Email
- Part 5: Core Data; UITableView and UICollectionView
Work with Files
Many beginner iOS developers stumble upon the following problem: the application works correctly in the simulator, but refuses to work or does not work correctly on a real device. One of the possible problems is that the application actively uses file resources that are not transferred to the application’s document folder. Apple's policy is this: you cannot change application files. The only place to have fun is the document folder. You can read from anywhere (within your application), but write only in the document folder.
So, at the first start, you need to transfer all the documents for writing to the document folder! Easy! Let's start by creating the Config.h file , add the following line to Your_App_Name-Prefix.pch (this file is automatically added to all files of your project):
#import "Config.h"
Excellent! Now everything in Config.h is in the whole project! Let's fill this file:
#define pathToApplicationDirectory [[NSBundle mainBundle] bundlePath] // Путь к папке приложения
#define pathToDocuments [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] // Путь к документам
#define pathToSettings [[pathToDocuments URLByAppendingPathComponent:@"settings.plist"] path] // Путь к файлу настроек
#define pathToPopups [[NSBundle mainBundle] pathForResource:@"popups" ofType:@"plist"] // Путь к файлу со всплывающими окнами
All this is done exclusively for our convenience in the future (we will work with files a lot and it would be nice to get rid of magic lines).
Now you can start copying files to the document folder. We will often change the settings.plist and popups.plist files , so a copy item is necessary. Add the following code to our application: didFinishLaunchingWithOptions:
// Я юзер, а не лузер, так что перемещу ресурсы к документам!
[self placeResourcesToDocumentsDirectory:@{
@"settings" : @"plist",
@"popups" : @"plist"}];
And, of course, the placeResourcesToDocumentsDirectory method itself:
/*!
Метод, копирующий файлы из словаря в папку с документами
/param Словарь с именами файлов
*/
- (void)placeResourcesToDocumentsDirectory:(NSDictionary *)resources {
// Проверим один из файлов, вдруг скопировано уже
if (![[NSFileManager defaultManager] fileExistsAtPath:pathToSettings) {
for (NSString *fileName in [resources allKeys]) {
NSString *extension = resources[fileName];
NSURL *storeURL = [pathToDocuments URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", fileName, extension]];
NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:fileName ofType:extension]];
NSError *error = nil;
[[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&error];
}
}
}
That's all! How simple it turned out to move the files to the desired folder, right? And you can access the file and work with it like this:
NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithContentsOfFile:pathToSettings];
settings[@"isThisAppCool"] = @YES;
[settings writeToFile:pathToSettings atomically:YES];
Singleton Template
It is rather a small snippet that can make it easier for you to work with single people.
Singleton.h :
<...>
// Упростим доступ к синглтону
#define coolSingleton [Singleton sharedSingleton]
@interface Singleton : NSObject
+ (Singleton *)sharedSingleton;
<...>
Singleton.m :
<...>
@implementation Singleton
+ (Singleton *)sharedSingleton {
static Singleton *sharedSingleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedSingleton = [[self alloc] init];
});
return sharedSingleton;
}
<...>
Everything is simple here. Firstly, we have simplified the work with a singleton in one definition. Secondly, there is one static object of the Singleton class. Third, by calling the method of the sharedSingleton class , we will either get an existing object or initialize a new one. There will always be only one singleton object. Fourth, our singleton is thread safe (thanks danilNik and AndrewShmig for the tip!).
Work with Audio
Here we have to work with the AVAudioPlayer class from the AVFoundation framework. The principle of operation is simple: create an object of the AVAudioPlayer class with the name of a specific file, prepare it for playback, and launch it when necessary. Let's create a simple singleton that will contain all of our audio players. We will have two audio players: one for background music, the second for playing the sound of a button click.
Take a look at SimpleAudioPlayer.h :
#import
#import
#define audioPlayer [SimpleAudioPlayer sharedAudioPlayer]
@interface SimpleAudioPlayer : NSObject
@property (nonatomic, retain) AVAudioPlayer *backgroundMusicPlayer;
@property (nonatomic, retain) AVAudioPlayer *buttonSoundPlayer;
+ (SimpleAudioPlayer *)sharedAudioPlayer;
@end
And on SimpleAudioPlayer.m :
#import "SimpleAudioPlayer.h"
@implementation SimpleAudioPlayer
static SimpleAudioPlayer *sharedAudioPlayer;
+ (SimpleAudioPlayer *)sharedAudioPlayer {
static SimpleAudioPlayer *sharedAudioPlayer = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedAudioPlayer = [[self alloc] init];
});
return sharedAudioPlayer;
}
- (id)init {
self = [super init];
if (self) {
[self initAudioPlayers];
}
return self;
}
/*!
Метод, инициализирующий аудиоплееры
*/
- (void)initAudioPlayers {
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:pathToBackgroundAudio];
self.backgroundMusicPlayer = [[AVAudioPlayer alloc]
initWithContentsOfURL:fileURL error:nil];
[self.backgroundMusicPlayer prepareToPlay];
self.backgroundMusicPlayer.numberOfLoops = -1;
fileURL = [[NSURL alloc] initFileURLWithPath:pathToButtonAudio];
self.buttonSoundPlayer = [[AVAudioPlayer alloc]
initWithContentsOfURL:fileURL error:nil];
[self.buttonSoundPlayer prepareToPlay];
}
That's all. The definitions of the paths to the audio files can be written in Config.h . It is worth noting that we indicated a negative number for the number of repetitions of background music. If you set a negative number for this property, then the audio file will repeat endlessly. What you need! Also, do not forget about the prepareToPlay method - if you prepare all the audio players as soon as the application is launched, there will not be a slight delay before the first playback of the audio file. And you can use our audio player like this:
[audioPlayer.backgroundMusicPlayer play];
<...>
[audioPlayer.backgroundMusicPlayer stop];
Work with video
Let's work a little with the MediaPlayer framework. In fact, the following code can even be added to the viewDidAppear method :
NSURL *url = [[NSURL alloc] initFileURLWithPath:pathToMovie];
MPMoviePlayerViewController *movieController = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
[self presentMoviePlayerViewControllerAnimated:movieController];
[movieController.moviePlayer play];
This code simply displays the video file at the given URL. Apple’s video player already has all the buttons you need to close the video player. So the four lines above are the minimum character set for incorporating video into your application.
In-App Purchases
Well, the most interesting thing for today: the StoreKit framework! True, we will not work directly with him, but with MKStoreKit . Many thanks to MugunthKumar for the great framework!
It's simple: edit the MKStoreKitConfigs.plist file to your needs (everything is intuitive there) and use the following code to verify purchases:
if ([MKStoreManager isFeaturePurchased:@"me.identifier.coolapp.somesinglefeature"]) {
// Юзер уже купил эту фишку! Нужно ее ему отдать!
}
if ([MKStoreManager isSubscriptionActive:@"me.identifier.coolapp.somesubscription"]) {
// Вау! Да мы можем тянуть деньги ежемесячно, еженедельно, ежедневно! Но сегодня юзер уже заплатил, отдадим ему заслуженную плюшку
}
To make purchases, use the following:
[[MKStoreManager sharedManager] buyFeature:@"me.identifier.coolapp.somesinglefeature"
onComplete:^(NSString* purchasedFeature,
NSData* purchasedReceipt,
NSArray* availableDownloads) {
NSLog(@"Юзер купил: %@", purchasedFeature);
}
onCancelled:^ {
NSLog(@"Юзер отказался покупать :( печально.");
}];
Conclusion
That's all for today. This part was pilot, in the following articles we will consider examples more interesting. For example, how to create a pop-up window so that our client can change its behavior directly in .xib files, without your participation.
I ask you to write about all the errors and inaccuracies of the article in my hub center .
PS If you want to cooperate with me, then my profile is on one of the major freelancer exchanges .
Should I continue the series of articles "Notes iOS developer"? Express your opinion in the comments.