iOS开发实战-上架AppStore 通过内购和广告获得收益
写在前面
由于一些原因需要离职,准备重回大上海
忽然发现手头上也没什么独立App,那就随便写个放到AppStore上吧,凑个数吧。哈哈哈。
这个App是无聊找配色的时候看到的一套图
正好春节在家没什么特别的事,编码用了半天左右吧,数据录入倒是也用了半天,于是就变成了这样。
上架的时候再做点效果图配点文字 就搞定了。
不得不说 我是白天提交的,到晚上就Review了
立马就通过了变 ready for sale了。。。
可能是App太过于简单了。也太好过审了。。。
广告版集成了google的Admob (需要搭梯子)不过测试发现模拟器能正常显示真机加了设备id也不能显示,经常空加载。。
最近申请了腾讯的广告 广点通 提交了新的版本。就是注册审核需要过个一两天,关联个APP要个一两天。不过效果还是有的。
2.6提交的 ,今天(2.7)正式过审,就有收益了,估计都是自己和apple测试的时候展示的。
大家可能也看到了,这是个很简单的App,无非就是几个列表展示下分类的颜色和收藏的颜色,担心功能太单一,所以又添加了自定义色。下面我们来讲 项目 Demo吧。
效果
分析
感觉都没什么好分析的了 ,就是个tableview自定义cell就行了 这里我直接用xib设置约束了
每个色块有3个btn btn的颜色都是从plist中读取,所以手工录入还是挺耗时间的。
自定义颜色方面 直接获取Touches的值做下计算
代码部分
这里就贴一个自定义颜色部分。通过block回调选择颜色的RGB值
#import <UIKit/UIKit.h>
@interface WSColorImageView : UIImageView
@property (copy, nonatomic) void(^currentColorBlock)(UIColor *color,NSString *rgbStr);
@end
#import "WSColorImageView.h"
@interface WSColorImageView()
@property (nonatomic,copy)NSString *rgbStr;
@end
@implementation WSColorImageView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, frame.size.width)];
if (self) {
self.image = [UIImage imageNamed:@"palette"];
self.userInteractionEnabled = YES;
self.layer.cornerRadius = frame.size.width/2;
self.layer.masksToBounds = YES;
}
return self;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint pointL = [touch locationInView:self];
if (pow(pointL.x - self.bounds.size.width/2, 2)+pow(pointL.y-self.bounds.size.width/2, 2) <= pow(self.bounds.size.width/2, 2)) {
UIColor *color = [self colorAtPixel:pointL];
if (self.currentColorBlock) {
self.currentColorBlock(color,self.rgbStr);
}
}
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint pointL = [touch locationInView:self];
if (pow(pointL.x - self.bounds.size.width/2, 2)+pow(pointL.y-self.bounds.size.width/2, 2) <= pow(self.bounds.size.width/2, 2)) {
UIColor *color = [self colorAtPixel:pointL];
if (self.currentColorBlock) {
self.currentColorBlock(color,self.rgbStr);
}
}
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint pointL = [touch locationInView:self];
if (pow(pointL.x - self.bounds.size.width/2, 2)+pow(pointL.y-self.bounds.size.width/2, 2) <= pow(self.bounds.size.width/2, 2)) {
UIColor *color = [self colorAtPixel:pointL];
if (self.currentColorBlock) {
self.currentColorBlock(color,self.rgbStr);
}
}
}
//获取图片某一点的颜色
- (UIColor *)colorAtPixel:(CGPoint)point {
if (!CGRectContainsPoint(CGRectMake(0.0f, 0.0f, self.image.size.width, self.image.size.height), point)) {
return nil;
}
NSInteger pointX = trunc(point.x);
NSInteger pointY = trunc(point.y);
CGImageRef cgImage = self.image.CGImage;
NSUInteger width = self.image.size.width;
NSUInteger height = self.image.size.height;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
int bytesPerPixel = 4;
int bytesPerRow = bytesPerPixel * 1;
NSUInteger bitsPerComponent = 8;
unsigned char pixelData[4] = { 0, 0, 0, 0 };
CGContextRef context = CGBitmapContextCreate(pixelData,
1,
1,
bitsPerComponent,
bytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextTranslateCTM(context, -pointX, pointY-(CGFloat)height);
CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, (CGFloat)width, (CGFloat)height), cgImage);
CGContextRelease(context);
CGFloat red = (CGFloat)pixelData[0] / 255.0f;
CGFloat green = (CGFloat)pixelData[1] / 255.0f;
CGFloat blue = (CGFloat)pixelData[2] / 255.0f;
CGFloat alpha = (CGFloat)pixelData[3] / 255.0f;
NSLog(@"%f***%f***%f***%f",red,green,blue,alpha);
NSLog(@"%d,%d,%d",pixelData[0],pixelData[1],pixelData[2]);
self.rgbStr = [NSString stringWithFormat:@"%d,%d,%d",pixelData[0],pixelData[1],pixelData[2]];
return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}
- (void)setImage:(UIImage *)image {
UIImage *temp = [self imageForResizeWithImage:image resize:CGSizeMake(self.frame.size.width, self.frame.size.width)];
[super setImage:temp];
}
- (UIImage *)imageForResizeWithImage:(UIImage *)picture resize:(CGSize)resize {
CGSize imageSize = resize; //CGSizeMake(25, 25)
UIGraphicsBeginImageContextWithOptions(imageSize, NO,0.0);
CGRect imageRect = CGRectMake(0.0, 0.0, imageSize.width, imageSize.height);
[picture drawInRect:imageRect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
return image;
}
@end
补充
给免费版添加了内购去广告功能。
使用的是本地数据库,自定义tableview的footview。
未购买标识为0,广告位的frame高设为44;
购买成功就将标识设为1,广告位frame高设为0;
都是tableview直接reload。
这里再给出内购的代码。
注意:设置成订阅类商品(非消耗)一定要添加恢复购买的代码 不然审核会被拒
#import <StoreKit/StoreKit.h>
@interface ColorFavTableViewController ()<SKPaymentTransactionObserver,SKProductsRequestDelegate>
#pragma mark - 内购
- (IBAction)clickPhures:(id)sender {
// [[CoreDataOperations sharedInstance] buyNoAds];
// [self.tableView reloadData];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:@"花费6元购买去广告" preferredStyle:UIAlertControllerStyleActionSheet];
[alertController addAction:[UIAlertAction actionWithTitle:@"确定购买" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSString *product = @"123";
_currentProId = product;
if([SKPaymentQueue canMakePayments]){
[self requestProductData:product];
}else{
NSLog(@"不允许程序内付费");
}
}]];
[alertController addAction:[UIAlertAction actionWithTitle:@"已购买直接去广告" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}]];
[alertController addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
//请求商品
- (void)requestProductData:(NSString *)type{
NSLog(@"-------------请求对应的产品信息----------------");
NSArray *product = [[NSArray alloc] initWithObjects:type,nil];
NSSet *nsset = [NSSet setWithArray:product];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
request.delegate = self;
[request start];
}
//收到产品返回信息
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
NSLog(@"--------------收到产品反馈消息---------------------");
NSArray *product = response.products;
if([product count] == 0){
NSLog(@"--------------没有商品------------------");
return;
}
NSLog(@"productID:%@", response.invalidProductIdentifiers);
NSLog(@"产品付费数量:%lu",(unsigned long)[product count]);
SKProduct *p = nil;
for (SKProduct *pro in product) {
NSLog(@"%@", [pro description]);
NSLog(@"%@", [pro localizedTitle]);
NSLog(@"%@", [pro localizedDescription]);
NSLog(@"%@", [pro price]);
NSLog(@"%@", [pro productIdentifier]);
if([pro.productIdentifier isEqualToString:_currentProId]){
p = pro;
}
}
SKPayment *payment = [SKPayment paymentWithProduct:p];
NSLog(@"发送购买请求");
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
//请求失败
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
NSLog(@"------------------错误-----------------:%@", error);
}
- (void)requestDidFinish:(SKRequest *)request{
NSLog(@"------------反馈信息结束-----------------");
}
//沙盒测试环境验证
#define SANDBOX @"https://sandbox.itunes.apple.com/verifyReceipt"
//正式环境验证
#define AppStore @"https://buy.itunes.apple.com/verifyReceipt"
/**
* 验证购买,避免越狱软件模拟苹果请求达到非法购买问题
*
*/
-(void)verifyPurchaseWithPaymentTransaction{
//从沙盒中获取交易凭证并且拼接成请求体数据
NSURL *receiptUrl=[[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData=[NSData dataWithContentsOfURL:receiptUrl];
NSString *receiptString=[receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//转化为base64字符串
NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", receiptString];//拼接请求数据
NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
//创建请求到苹果官方进行购买验证
NSURL *url=[NSURL URLWithString:AppStore];
NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url];
requestM.HTTPBody=bodyData;
requestM.HTTPMethod=@"POST";
//创建连接并发送同步请求
NSError *error=nil;
NSData *responseData=[NSURLConnection sendSynchronousRequest:requestM returningResponse:nil error:&error];
if (error) {
NSLog(@"验证购买过程中发生错误,错误信息:%@",error.localizedDescription);
return;
}
NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];
NSLog(@"%@",dic);
if([dic[@"status"] intValue]==0){
NSLog(@"购买成功!");
[[CoreDataOperations sharedInstance] buyNoAds];
[self.tableView reloadData];
NSDictionary *dicReceipt= dic[@"receipt"];
NSDictionary *dicInApp=[dicReceipt[@"in_app"] firstObject];
NSString *productIdentifier= dicInApp[@"product_id"];//读取产品标识
//如果是消耗品则记录购买数量,非消耗品则记录是否购买过
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
if ([productIdentifier isEqualToString:@"123"]) {
int purchasedCount=[defaults integerForKey:productIdentifier];//已购买数量
[[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:productIdentifier];
}else{
[defaults setBool:YES forKey:productIdentifier];
}
//在此处对购买记录进行存储,可以存储到开发商的服务器端
}else{
NSLog(@"购买失败,未通过验证!");
}
}
//监听购买结果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{
for(SKPaymentTransaction *tran in transaction){
switch (tran.transactionState) {
case SKPaymentTransactionStatePurchased:{
NSLog(@"交易完成");
[self verifyPurchaseWithPaymentTransaction];
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
[[CoreDataOperations sharedInstance] buyNoAds];
[self.tableView reloadData];
}
break;
case SKPaymentTransactionStatePurchasing:
NSLog(@"商品添加进列表");
break;
case SKPaymentTransactionStateRestored:{
NSLog(@"已经购买过商品");
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
[[CoreDataOperations sharedInstance] buyNoAds];
[self.tableView reloadData];
}
break;
case SKPaymentTransactionStateFailed:{
NSLog(@"交易失败");
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}
break;
default:
break;
}
}
}
//交易结束
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
NSLog(@"交易结束");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)dealloc{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
Demo地址
广告版免费,收费版1元,有兴趣的可以下来玩玩
当然,Github公开无广告版的源码,欢迎点赞加星
iOS开发实战-上架AppStore 通过内购和广告获得收益的更多相关文章
- iOS开发——实战OC篇&环境搭建之Xib(玩转UINavigationController与UITabBarController)
iOS开发——实战OC篇&环境搭建之Xib(玩转UINavigationController与UITabBarController) 前面我们介绍了StoryBoard这个新技术,和纯技术 ...
- iOS开发——实战OC篇&环境搭建之纯代码(玩转UINavigationController与UITabBarController)
iOS开发——实战OC篇&环境搭建之纯代码(玩转UINavigationController与UITabBarController) 这里我们就直接上实例: 一:新建一个项目singleV ...
- Xamarin iOS开发实战第1章使用C#编写第一个iOS应用程序
Xamarin iOS开发实战第1章使用C#编写第一个iOS应用程序 C#原本是用来编写Windows以及Windows Phone的应用程序.自从Xamarin问世后.C#的作用就发生了非常大的变化 ...
- 《iOS开发实战 从入门到上架App Store(第2版)》书籍目录
第1章 开发准备 1.1 iOS 10新特性简述 1.1.1 新增触觉反馈编程接口 1.1.2 SiriKit框架的开放 1.1.3 引入Messages App 1.1.4 通知框架的整合与扩展 1 ...
- iOS开发实战-基于SpriteKit的FlappyBird小游戏
写在前面 最近一直在忙自己的维P恩的事情 公司项目也是一团乱 于是...随手找了个游戏项目改了改就上线了,就当充数了. SpriteKit简介 SpriteKit是iOS 7之后苹果推出的2D游戏框架 ...
- 苹果iOS App上架流程,非iOS开发人员上架教程
iOS应用上线发布流程一般包含相关证书文件的配置.Xcode的设置.App Store Connect填写App的相关信息.ipa包上传.审核结果以及相关邮件回复.相关证书文件的配置与Xcode的 ...
- iOS开发——实战OC篇&环境搭建之StoryBoard(玩转UINavigationController与UITabBarController)
环境搭建之StoryBoard(玩转UINavigationController与UITabBarController) 研究了这么就IOS开发,都没有所处一个像样或者自己忙一点的项目.最近自 ...
- iOS 开发之路(WKWebView内嵌HTML5之图片上传) 五
HTML5页面的图片上传功能在iOS端的实现. 首先,页面上用的是plupload组件,在wkwebview上存在两个坑需要修复才能正常使用. 问题:在webview上点击选择照片/相机拍摄,就会出现 ...
- iOS开发app上架流程之证书的制作
1.证书的制作:登陆 https: 1.1appid的注册 选择Identifiers 下的App IDs然后如图所示 点击加号,进入 App ID Description下的Name:这个是appI ...
随机推荐
- 高防TTCDN
TCDN是深圳市云中漫网络科技公司高防CDN产品的品牌名称,既可以防御,也可以达到加速的效果,价格实惠.TTCDN适用于WEB应用,可以隐藏源站服务器IP,有效的减轻源站服务器压力,加快全国各地区线路 ...
- python加载sqlite3报错:No module named _sqlite3
环境为Ubuntu16.04 Apache2.4 Python2.7.13 django 1.8 今天部署apache+django,经过各种折腾,好不容易配置完了,发现错误Apache的日志里有一项 ...
- iptables实用教程(二):管理链和策略
概念和原理请参考上一篇文章"iptables实用教程(一)". 本文讲解如果管理iptables中的链和策略. 下面的代码格式中,下划线表示是一个占位符,需要根据实际情况输入参数, ...
- 关于前后端同构,我的一点思路和心得(vue、nodejs、react、模版、amd)
最近1年多,前后端同构慢慢变成一个流行词,也许很多人还停留在前后端分离的最佳实践道路上,但实际上又有一批人已经从简单的服务端渲染走向探索最佳前后端同构方案的路上了.不过,我只是膜拜后者的过客. 虽然大 ...
- 关于数据库优化1——关于count(1),count(*),和count(列名)的区别,和关于表中字段顺序的问题
1.关于count(1),count(*),和count(列名)的区别 相信大家总是在工作中,或者是学习中对于count()的到底怎么用更快.一直有很大的疑问,有的人说count(*)更快,也有的人说 ...
- Hibernate入门(一)
一 Hibernate介绍 Hibernate 是一个开源.轻量级的ORM(对象关系映射)工具,该工具简化了数据创建.数据处理和数据访问,它是一种将对象映射到数据库中表的编程技术.ORM工具内部使用J ...
- MongoDB--架构搭建(主从、副本集)之副本集
任何时间点只有一个活跃节点,其他为备份节点,当活跃节点泵机,将会通过选举规则,从备选节点选一个当活跃节点,当泵机的节点恢复之后,则变为备用节点. 节点类型 stabdard:常规节点,存储完整数据,参 ...
- PHPCMS V9里加入JS时生成首页出错
有次在首页中加入JS,确认JS没有问题,但是在后台生成首页的时候一直出错. 查了半天才发现原来是JS里的“{}”问题,V9里调用内容也是用的大括号,冲突了. 解决方法是在“{”前面和后面分别加一个空格 ...
- react-native —— 在Mac上配置React Native Android开发环境排坑总结
配置React Native Android开发环境总结 1.卸载Android Studio,在终端(terminal)执行以下命令: rm -Rf /Applications/Android\ S ...
- Python的多线程编程
提到多线程,很多人就会望而却步,本文将由浅入深地带你攻克python多线程编程,并防止你跳入深坑, 首先看一段简单的代码: from time import ctime,sleep def play_ ...