iOS 有效「阅读源码」的一些思考

GitHub Repo:coderZsq.project.ios
Follow: coderZsq · GitHub
Resume: coderzsq.github.io/coderZsq.pr…

日常扯淡

上一篇是第一次写纯阅读类文章, 没有贴出的任何的代码块, 也没有写任何关于技术实现, 但反响出乎意料的好, 不仅获得了掘金的年度征文人气奖, 也得到了iOS大神南峰子的肯定并转载到了知识小集, 对此只有感恩和加倍的努力.

是的, 上一篇所提出的三个问题, 的确是每个开发者都该认真思考的, 但光有价值观, 没有方法论可不行, 有好多读者私信我想要知道前方的路在哪, 想必这也是困扰你许久的症结.

所以这一篇, 我们来谈谈如何进行快速的学习并使知识为我所用, 想要学习真正的技术, 光看技术博客可不行, 这一点在上一篇文章也有所涉及, 如何有效的学习技术, 必然的途径就是看官方文档和阅读优秀的三方源码.

源码区别

首先我们要了解官方源码和三方源码的区别, 到底看官方源码还是优秀的三方源码对成长的效率高, 这里要理解一个概念, 官方源码, 顾名思义对于iOS来说, 就是系统级别的代码, 就是官方会一直维护支持的, 而三方源码就是优秀的开发者开源出来的代码, 可能很优秀, 但并不能保证长时间的有效更新, 打个比方当你的iPhoneXs屏幕破碎了, 你可以到苹果天才吧进行维修, 或者到一些小作坊进行维修, 这里可以类比优秀的三方源码就是手艺高超的手艺人.

知道了区别, 我的建议是先看官方的源码, 这里不仅仅是iOS, 因为所有的三方源码都是大部分都是参照官方源码进行设计的, 也不乏优秀的三方源码被苹果爸爸相中集成到官方源码中, 但这毕竟是少数, 而且我对于三方源码一直是持有保留意见的.

难易程度

怎么选源码对学习的效率也有讲究么? 是的, 而且大有关系, 如果你是iOS开发, 想必你肯定用过AFNetWorking, SDWebImage, 这种牛逼哄哄的框架, 但如果你基础不是很扎实, 就算官方给出了架构文档, 你是不是感觉也无从下手? 就算看了个大概, 也不知道为什么要这样设计, 当然这两个框架网上有太多的文章来进行源码解读, 我也不会作出狂贴代码这种那么Low的事情, 因为所有的选择都是在特定的上下文之下才能观其端倪, 这也就是场景的重要性, 简单来说, 就是要输出倒逼输入, 在场景中想问题, 逆向思考, 你会得出不一样的结论.

还有很重要的一点是, 那你所看的源码需要和那你的能力所匹配, 比如说, 你并不懂C++, 而要看runtime源码你就会感觉很蛋疼, 如果你不懂汇编, 看消息发送那段就算有注释都自觉天书.

所以我的建议是可以看一些比你能力强一点点的, 稍微努努力能够够着的源码, 如果你感觉什么源码都入手困难的话, 可能你的语言基础太过薄弱, 还是乘早转行吧, 因为现在的环境下, 看不了源码几乎没有什么生存空间了.

一些趣事

入手之前, 我们先要了解一个概念, 并不是只有1000star以上的源码才有阅读的必要, 阅读和使用是两个不同的概念, 使用在项目中我们需要的是稳定性, 所以没有大量验证的代码是不能集成到项目中去的, 而阅读就不一样, 阅读是用来学习思想的, 理解了别人写的代码, 自己写一个, 就会觉得所谓的三方库也就不过尔尔.

这里挑选一个库ZIKViper, 可能大家并不了解这个库, 也跟别提用过这个库了, 在github上也就不到200个star, 作者也好像不怎么维护这个库了, 但这都不是重点, 重点是这我们能够从这个源码中学习到一些关于Viper架构优秀的设计思路, 以及将这些经验运用到自己的项目中去, 这才是我们看源码应该有的角度.

为什么选这个源码, 首先最近拟定要进行架构升级, 我之前也写过一篇关于架构的文章, 但现在回看还是非常幼稚和脆弱的, 但是这也证明了我的成长, 当你感觉过去的代码你看不下去了, 恭喜你, 你进步了.

有缘的是, 在我写之前的架构的时候还和ZIKViper的作者有一次交流, 我将交流整理成文章放在了网上可以点击查阅: iOS 关于组件化Router设计的争辩, 希望对你有所帮助.

了解概念

既然选择了一个源码, 在入手之前, 我们需要了解这个库到底是用来做什么的, 即场景的重要性. 可以看下相关库的README.md.

VIPER简介

VIPER的全称是View-Interactor-Presenter-Entity-Router。示意图如下:

相比MVX架构,VIPER多出了两个东西:Interactor(交互器)和Router(路由)。

各部分职责如下:

View 提供完整的视图,负责视图的组合、布局、更新 向Presenter提供更新视图的接口 将View相关的事件发送给Presenter Presenter 接收并处理来自View的事件 向Interactor请求调用业务逻辑 向Interactor提供View中的数据 接收并处理来自Interactor的数据回调事件 通知View进行更新操作 通过Router跳转到其他View Router 提供View之间的跳转功能,减少了模块间的耦合 初始化VIPER的各个模块 Interactor 维护主要的业务逻辑功能,向Presenter提供现有的业务用例 维护、获取、更新Entity 当有业务相关的事件发生时,处理事件,并通知Presenter Entity 和Model一样的数据模型 和MVX的区别 VIPER把MVC中的Controller进一步拆分成了Presenter、Router和Interactor。和MVP中负责业务逻辑的Presenter不同,VIPER的Presenter的主要工作是在View和Interactor之间传递事件,并管理一些View的展示逻辑,主要的业务逻辑实现代码都放在了Interactor里。Interactor的设计里提出了"用例"的概念,也就是把每一个会出现的业务流程封装好,这样可测试性会大大提高。而Router则进一步解决了不同模块之间的耦合。所以,VIPER和上面几个MVX相比,多总结出了几个需要维护的东西:

View事件管理 数据事件管理 事件和业务的转化 总结每个业务用例 模块内分层隔离 模块间通信 而这里面,还可以进一步细分一些职责。VIPER实际上已经把Controller的概念淡化了,这拆分出来的几个部分,都有很明确的单一职责,有些部分之间是完全隔绝的,在开发时就应该清晰地区分它们各自的职责,而不是将它们视为一个Controller。

优点 VIPER的特色就是职责明确,粒度细,隔离关系明确,这样能带来很多优点:

可测试性好。UI测试和业务逻辑测试可以各自单独进行。 易于迭代。各部分遵循单一职责,可以很明确地知道新的代码应该放在哪里。 隔离程度高,耦合程度低。一个模块的代码不容易影响到另一个模块。 易于团队合作。各部分分工明确,团队合作时易于统一代码风格,可以快速接手别人的代码。

好了, 以上都是抄的, 都是Zuikyo的文字, 我仅仅是粘贴使用, 想必他也不会介意的吧hhh

准备入手

了解了源码的概念, 我们就该开始着手阅读了, 首先将库克隆岛本地, pod install什么的常规操作我就不说了.

首先我们要看的是主文件

#import <UIKit/UIKit.h>
#import "SQViperView.h"
#import "SQViperViewEventHandler.h"
#import "SQViperPresenter.h"
#import "SQViperInteractor.h"
#import "SQViperWireframe.h"
#import "SQViperRouter.h"
#import "SQViperViewPrivate.h"
#import "SQViperPresenterPrivate.h"
#import "SQViperInteractorPrivate.h"
#import "SQViperWireframePrivate.h"
#import "NSObject+SQViperAssembly.h"
#import "UIViewController+SQViperRouter.h"

FOUNDATION_EXPORT double SQViperVersionNumber;

FOUNDATION_EXPORT const unsigned char SQViperVersionString[];
复制代码

这里你会发现和ZIK的前缀不同, 没关系, 我就是抄的, 在学习别人源码的过程中, 最有效的方法就是把别人的代码从新敲上一遍, 你会有不同的感悟, 在此声明我一直都是原创的簇拥者, 此篇文章所表达的意思是如何学习源码, 这里仅仅是代码举例.

你是不是也发现了优秀的代码都是极为的相似, 优秀是有范式, 有迹可循的.

重点分析

我这里不会长篇的贴代码进行解读, 想必看到这里, 你一定是有一定经验阅读源码的人, 所以文末放出地址, 自己去看就是了.

这里我们重点关注的是为什么要这样设计, 这样设计的好处在哪里.

看了主头文件, 我们可以发现所有协议后有加上Private字样. 以SQViperView举例.

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@protocol SQViperViewEventHandler;

@protocol SQViperView <NSObject>

- (nullable UIViewController *)routeSource;

@property (nonatomic, readonly, strong) id<SQViperViewEventHandler> eventHandler;

@optional
@property (nonatomic, readonly, strong) id viewDataSource;

@end

NS_ASSUME_NONNULL_END
复制代码
#import "SQViperView.h"

NS_ASSUME_NONNULL_BEGIN

@protocol SQViperViewEventHandler;

@protocol SQViperViewPrivate <SQViperView>

- (id<SQViperViewEventHandler>)eventHandler;

- (void)setEventHandler:(id<SQViperViewEventHandler>)eventHandler;

@optional
- (id)viewDataSource;

- (void)setViewDataSource:(id)viewDataSource;

- (void)setRouteSource:(UIViewController *)routeSource;

@end

NS_ASSUME_NONNULL_END
复制代码

这里很清楚的就能够看到, 这是表示权限问题, 也就是只读和读写的问题, 这个在架构设计上尤为重要, 也是工作经验的体现.

#import "NSObject+SQViperAssembly.h"

@implementation NSObject (SQViperAssembly)

+ (void)assembleViperForView:(id<SQViperViewPrivate>)view presenter:(id<SQViperPresenterPrivate>)presenter interactor:(id<SQViperInteractorPrivate>)interactor wireframe:(id<SQViperWireframePrivate>)wireframe router:(id<SQViperRouter>)router {
    
    NSParameterAssert([view conformsToProtocol:@protocol(SQViperView)]);
    NSParameterAssert([presenter conformsToProtocol:@protocol(SQViperPresenter)]);
    NSParameterAssert([interactor conformsToProtocol:@protocol(SQViperInteractor)]);
    NSParameterAssert([wireframe conformsToProtocol:@protocol(SQViperWireframe)]);
    NSParameterAssert([router conformsToProtocol:@protocol(SQViperRouter)]);
    
    NSAssert3(interactor.eventHandler == nil, @"Interactor (%@)'s eventHandler (%@) already exists when assemble viper for new eventHandler", interactor, interactor.eventHandler, presenter);
    NSAssert3(interactor.dataSource == nil, @"Interactor (%@)'s dataSource (%@) already exists when assemble viper for new dataSource", interactor, interactor.dataSource, presenter);
    
    interactor.eventHandler = presenter;
    interactor.dataSource = presenter;
    
    NSAssert3(wireframe.view == nil, @"Wireframe (%@)'s view (%@) already exists when assemble viper for new view", wireframe, wireframe.view, view);
    
    wireframe.view = view;
    wireframe.router = router;
    
    NSAssert3(presenter.interactor == nil, @"Presenter (%@)'s interactor already exists when assemble viper for new interactor", presenter, presenter.interactor, interactor);
    NSAssert3(presenter.view == nil, @"Presenter (%@)'s view already exists when assemble viper for new view", presenter, presenter.view, view);
    NSAssert3(presenter.wireframe == nil, @"Presenter (%@)'s wireframe (%@) already exists assemble viper for new router", presenter, presenter.wireframe, self);
    
    presenter.interactor = interactor;
    presenter.view = view;
    presenter.wireframe = wireframe;
    
    if ([view respondsToSelector:@selector(viewDataSource)] &&
        [view respondsToSelector:@selector(setViewDataSource:)]) {
        NSAssert3(view.viewDataSource == nil, @"View (%@)'s viewDataSource (%@) already exists when assemble viper for new viewDataSource", view, view.viewDataSource, presenter);
        view.viewDataSource = presenter;
    }
    
    NSAssert3(view.eventHandler == nil, @"View (%@)'s eventHandler (%@) already exists when assemble viper for new eventHandler", view, view.eventHandler, presenter);
    
    view.eventHandler = presenter;
}

@end
复制代码

这个分类很好的将架构的所有逻辑结构进行了组装, 堪称是这个源码的精髓所在.

其他的就没有什么值得称道的东西了, 毕竟这是一份简单的源码. 我们的关注点在思想上.

这里要说的是, 根据不同的源码了解分析出其关键点, 并了解设计思路.

动手实践

所谓实践才是检验真理的唯一标准, 所以一般优秀的三方源码都会有demo作为参考, 所以想要学习的同学, 可以自行查阅.

我这里对比之前我写的容量增肌项目, 对比写了一个Viper版本的作为对照, 也会把碰到的问题分享给大家.

#import "SQTrainingCapacityViewController.h"
#import "SQTrainingCapacityCell.h"
#import "SQTrainingCapacityHeaderView.h"
#import "SQTrainingCapacityFooterView.h"
#import "SQTrainingCapacityCellPresenter.h"
#import "SQTrainingCapacityDataSource.h"
#import "SQTrainingCapacityViewEventHandler.h"

@interface SQTrainingCapacityViewController ()

@property (nonatomic, strong) NSMutableArray *dataSource;
@property (nonatomic, weak) SQTrainingCapacityFooterView * footerView;

@end

@implementation SQTrainingCapacityViewController

- (void)setupData {
    _dataSource = [NSMutableArray array];
}

- (void)fetchDataSource {
    NSArray *dataSource = [(id<SQTrainingCapacityDataSource>)self.viewDataSource fetchDataSourceFromDB];
    [self.dataSource removeAllObjects];
    [self.dataSource addObjectsFromArray:dataSource];
}

- (void)setupUI {
    [self setupRightBarButtonItem];
    [self setupTableView];
}

- (void)setupRightBarButtonItem {
    [self setRightBarButtonItem:(UIBarButtonSystemItemAdd) target:self action:@selector(addTraningAction)];
}

- (void)setRightBarButtonItem:(UIBarButtonSystemItem)item target:(nullable id)target action:(nullable SEL)action {
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:item target:target action:action];
}

- (void)setupTableView {
    self.tableView.tableHeaderView = [SQTrainingCapacityHeaderView headerView];
    SQTrainingCapacityFooterView * footerView = [SQTrainingCapacityFooterView footerView];
    footerView.totalCapacityLabel.text = [(id<SQTrainingCapacityDataSource>)self.viewDataSource totalCapacity];
    self.tableView.tableFooterView = footerView;
    self.footerView = footerView;
    [self.tableView registerNib:[UINib nibWithNibName:@"SQTrainingCapacityCell" bundle:[NSBundle bundleForClass:self.class]] forCellReuseIdentifier:@"TrainingCapacity"];
    [self setupDataSource:self.dataSource loadCell:^UITableViewCell *(UITableView * _Nonnull tableView, NSIndexPath * _Nonnull indexPath) {
        return [tableView dequeueReusableCellWithIdentifier:@"TrainingCapacity" forIndexPath:indexPath];
    } loadCellHeight:^CGFloat(id  _Nonnull model) {
        return 160;
    } bind:^(UITableViewCell * _Nonnull cell, id  _Nonnull model) {
        SQTrainingCapacityCell * c = (SQTrainingCapacityCell *)cell;
        SQTrainingCapacityCellPresenter * p = (SQTrainingCapacityCellPresenter * )model;
        [p bindToCell:c];
    }];
}

- (void)addTraningAction {
    [(id<SQTrainingCapacityViewEventHandler>)self.eventHandler didTouchNavigationBarAddButton];
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    [(id<SQTrainingCapacityViewEventHandler>)self.eventHandler handleCommitEditingAtIndexPath:indexPath];
}

@end
复制代码

这里仅仅贴出控制器的代码, 我们可以看到, 在Viper架构下, 获取数据和操作逻辑都通过协议进行传递, 没有任何冗余的代码, 分层清晰明了.

#import "SQTrainingCapacityPresenter.h"
#import "SQTrainingCapacityViewProtocol.h"
#import "SQTrainingCapacityInteractorInput.h"
#import "SQTrainingCapacityWireframeInput.h"
#import "SQTrainingCapacityNotification.h"

@interface SQTrainingCapacityPresenter ()

@property (nonatomic, weak) id<SQTrainingCapacityViewProtocol> view;
@property (nonatomic, strong) id<SQTrainingCapacityInteractorInput> interactor;
@property (nonatomic, strong) id<SQTrainingCapacityWireframeInput> wireframe;

@end

@implementation SQTrainingCapacityPresenter

- (void)handleViewReady {
    NSAssert(self.wireframe, @"Router should be initlized when view is ready.");
    NSAssert([self.view conformsToProtocol:@protocol(SQViperView)], @"Presenter should be attach to a view");
    NSAssert([self.interactor conformsToProtocol:@protocol(SQViperInteractor)], @"Interactor should be initlized when view is ready.");
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
    [self.interactor loadDataSourceWithTitle:self.view.title type:self.view.type];
    [self.view fetchDataSource];
}

- (void)handleViewWillAppear:(BOOL)animated {
    NSLog(@"%s", __func__);
}

- (void)handleViewDidAppear:(BOOL)animated {
    NSLog(@"%s", __func__);
}

- (void)handleViewWillDisappear:(BOOL)animated {
    NSLog(@"%s", __func__);
}

- (void)handleViewDidDisappear:(BOOL)animated {
    NSLog(@"%s", __func__);
}

- (void)handleViewRemoved {
    NSLog(@"%s", __func__);
    [self.interactor storeDataSourceWithTitle:self.view.title type:self.view.type dataSource:self.fetchDataSourceFromDB];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)handleCommitEditingAtIndexPath:(NSIndexPath *)indexPath {
    NSMutableArray *tempDataSource = self.fetchDataSourceFromDB.mutableCopy;
    [tempDataSource removeObjectAtIndex:indexPath.row];
    [self.interactor storeDataSourceWithTitle:self.view.title type:self.view.type dataSource:tempDataSource];
    [self.view fetchDataSource];
    [self.view.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
    self.view.footerView.totalCapacityLabel.text = self.totalCapacity;
}

- (void)didTouchNavigationBarAddButton {
    [self.interactor addTrainingAction];
    [self.view fetchDataSource];
    [self.view.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.fetchDataSourceFromDB.count - 1 inSection:0]] withRowAnimation:(UITableViewRowAnimationLeft)];
}

- (void)didTouchNavigationBarDoneButton {
    [self.view.tableView endEditing:YES];
    [[NSNotificationCenter defaultCenter]postNotificationName:SQTrainingCapacityBindToModelNotification object:nil];
    self.view.footerView.totalCapacityLabel.text = self.totalCapacity;
    [self.view.tableView reloadData];
}

- (void)keyboardWillShow:(NSNotification *)sender {
    [self.view setRightBarButtonItem:(UIBarButtonSystemItemDone) target:self action:@selector(didTouchNavigationBarDoneButton)];
}

- (void)keyboardWillHide:(NSNotification *)sender {
    [self.view setRightBarButtonItem:(UIBarButtonSystemItemAdd) target:self action:@selector(didTouchNavigationBarAddButton)];
}

- (NSString *)totalCapacity {
    return self.interactor.totalCapacity;
}

- (NSArray *)fetchDataSourceFromDB {
    return self.interactor.fetchDataSource;
}

@end
复制代码

协调整个架构的presenter, 逻辑一下子也清晰了很多, 和之前的进行对比, 你会更好的了解这个架构.

如何实践, 文档是讲不清楚的, 需要大家阅读源码和动手实践

GitHub: github.com/coderZsq

项目地址: github.com/coderZsq/co…

笔记地址: github.com/coderZsq/co…

写在最后

这篇分析了如何阅读源码的全过程, 简单来说就是, 确定场景, 了解概念, 重点分析, 动手实践, 这些东西说说简单, 实际操作中还是有很多的精妙之处的

以上.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
苹果iOS是由苹果公司开发的手持设备操作系统。苹果公司最早于2007年1月9日的Macworld大会上公布这个系统,最初是设计给iPhone使用的,后来陆续套用到iPod touch、iPad以及Apple TV等苹果产品上。iOS与苹果的Mac OS X操作系统一样,它也是以Darwin为基础的,因此同样属于类Unix的商业操作系统。原本这个系统名为iPhone OS,直到2010年6月7日WWDC大会上宣布改名为iOS。截止至2011年11月,根据Canalys的数据显示,iOS已经占据了全球智能手机系统市场份额的30%,在美国的市场占有率为43%。 源码列表: 按钮类 按钮 Drop Down Control 按钮-Circular Music Player Control 》》Flat Pill Button 按钮类--Fancy Menu 按钮之Custom Gradient Button 按钮之Flat Button 按钮之NIDropDown 按钮之Popup Menu 按钮之UIMenuItem with Image 标签类 标签之Emoji Label 标签之Justifier Label 标签之Top Aligning Label 标签之单一label多颜色多字体 弹出视图 弹出视图 View Bounce Animation 弹出视图(Popup View)UIPopoverListView 弹出视图(Popup View)之URBAlertView 弹出视图(Popup View)之自定义菜单UIMenuBar 弹出视图-Patterned Alert View 弹出视图-Table Alert 弹出视图类--Blur ModalView 弹出视图类--Depth View 弹出视图类--FWTPopover 弹出视图类--icon sheet 弹出视图类--Informatic Toolbar 弹出视图类--WCAlertView 弹出视图之Depth Modal 弹出视图之SelectViewController 导航条 导航条(Navigation Bar)Navigation Menu 导航条(Navigation Bar)之Menu on NavigationBar 导航条类--iOS更换皮肤 导航条之Breadcrumb View 导航条之NavBarNotificationView 导航条之NavigationController Transition 导航条之Title Swipe View 导航条之Title View on NavigationBar 地图类--自定义地图标注 地图(Map)之SJOPaperboy 地图类--Custom Annotation 地图类--DirectionsKit 地图类--Location Indicator 地图类--简单的地图路径 地图类--自定义地图标注 动画类 动画--Spring LoadedView 动画-UIKitForGame 动画类--Guide Arrow 动画之Animation Sequence 动画之Genie Effect 动画之Steam View 分段选择类 分段选择(Segment)之URBSegmentedControl 分段选择类--SVSegmentedControl 扩展 分段选择之AKSegmentedControl 分段选择之Color Bar Segments 滚动视图 滚动的标签 滚动视图(ScrollView)Scroll Grid Controller 滚动视图--Infinite GridView 滚动视图类--CoverFlow 滚动视图类-Lazy ScrollView 滚动视图类--Parallax ScrollView 滚动视图类--Parallax View 滚动视图类-简单的广告栏 滚动视图类--拖动UIScrollView放大图片 滚动视图之Extended UIScrollView 滚动视图之MCPagerView 滚动视图之Page Scrubber Bar 滚动视图之Parallel View 滚动视图之SBFlowView 滚动视图之Wheel Component 汉字转换为拼音 滑竿类 滑杆(Slider)Circular Slider View 滑杆(Slider)之Range Selector 滑杆(Slider)之Volume Bar 滑杆类-Vertical Slider View 滑杆之Slider popover 滑杆之Trim Control 绘图类--My Palette 绘图类--超级简单的画正方形的方法 绘图之Drawing View 基于FMDB的数据库操作 简单阅读器 键盘类 键盘(Keyboard)之自定义表情键盘 键盘-FaceBoard 键盘-Keyboard Bar TextField 键盘类》》Number PadView 键盘类》》ZenKeyboard 键盘类--自定义的拨号键盘 键盘之Custom iOS Keyboard 进图条类 进度条(Progress)Circular Progress View 进度条(Progress)之Progress Toolbar 进度条-Colorful ProgressView 进度条类--Range Slider With Progress 进度条之circular timer 进度条之MCProgressView 开关类 开关(Switch)之RESwitch 开关-Simple Switch Demo 开关之Toggle View 开关之TTSwitch 列表类 联系人搜索算法 列表 纵向Table里嵌套横向Table 列表(Table)Cell Flip Segue 列表(Table)之DynamicHeights 列表(Table)之Expansion Table 列表(Table)之Horizontal table 列表(Table)之Pull Up To Refresh 列表(Table)之TableView with SearchBar 列表(Table)之UITable嵌套UITable 列表(Table)之UploadProgressView 列表-Rainbow Styled Pull To Refresh 列表-UITableView背景随动 列表类》》自定义Table View折叠效果 列表类-FormInputAccessoryView 列表类-Grid TableView 列表类-Grouped TableView With Shadows 列表类--iOS 6.0 Pull to Refresh 列表类--Section Selection View 列表类--Styled TableViewCell 列表类--TableView的各种操作 列表类--UIListView 列表类--下拉刷新加载SQLite数据 列表-让TableView的子view保持固定 列表之ExpansionTableView 列表之iOS Tree Component 列表之Refresh Control 列表之首列固定的列表 日历类 日历(Calendar)之Calendar Picker 日历(Calendar)之TimesSquare 日历之CalendarView 日历控件 日历之封装的My97DatePicker日历 社交类 社交分享-SinaWeibo Share 社交分享类》》ios6 Share Demo 社交分享类--Social Share TableViewCell 社交分享之KRShare 社交分享之MessageActivities 社交分享之ShareSDK 视图布局类 视图布局(View Layout)Border View 视图布局(View Layout)之Linear Layout View 视图布局(View Layout)之Quilt Layout 视图布局(View Layout)之模仿ness伸缩效果 视图布局-Animated Grid 视图布局-Note ViewController 视图布局-Side bar demo 视图布局-Sliding Grid View 视图布局类》》Circle Layout 视图布局类-HGPhoto Wall 视图布局类--Scaling For iPad mini 视图布局类--Swipe ViewController 视图布局之Cycled Viewer 视图布局之Dragging Buttons 视图布局之Mosaic UI 视图布局之Rounded View 视图手势切换 视图切换(View Transition)GuideViewController 视图切换类-3D浏览器 视图切换类--zaker应用进入画面效果 视图切换之视图切换大小渐变效果 手势交互--物体根据重力感应运动 手势交互之Drag View 手势交互之Touch Visualizer 图表类 图表--百分比圆环 图表类--Percentage Chart 图表类--极简个税计算器 图表之Rotation Pie Chart 图表之实时更新的曲线图 图像类 图像(Image)Transition ImageView 图像(Image)之Colorized Progress View 图像-AsyncImageView 图像-Blurred Image 图像-iOS Image Editor 图像-NLImageCropper 图像-Scratch View 图像类 -Photo Zoom 图像类》》360 Degree Panorama 图像类--Before After 图像类--Crop Image 图像类--Image Category 图像类--Image Select and Crop 图像类--OLImageView 图像类View With Bordered Image 图像类--图片下载和保存 图像之AmazeKit 图像之Croppable View 图像之ImagePickerController of InstaPDF 图像之ImageView With Preview 图像之Media Focus Manager 图像之Multiple Image Picker 网络类 网络类--Downloader Management 网络类--photo批量上传ftp 网络类--Reachability 网络之Multi Downloader 文字视图类 文字视图(Text)之AutoComplete TextField 文字视图(Text)之Bar Track ball Item 文字视图(Text)之Messages TableViewController 文字视图(Text)之TextView Placeholder 文字视图-HashTag Mention Controller 文字视图-Tweet Label 文字视图类--Digit Input 文字视图类--emoji-converter 文字视图类--Placeholder TextView 文字视图类--Swipe Shift Caret 文字视图之Autocomplete UITextField 文字视图之Clickable Label 文字视图之Hyperlink Label 文字视图之Note View 文字视图之Rich Content Label 相机类 相机-iOSMp4Camera 相机类>>Camera多张拍摄Demo 相机类--flash light 相机之实用手电筒 选项卡类 选项卡之AKTabBarController 选项卡之Arc Tab 选项卡之LSTabs 选择器类 选择器类--PickerView with Search Bar 选择器类--Value Selector 选择器类--Wheel Menu 选择器类--老虎机 选择器之定制多选的PickerView 音频声效类 音频声效 VoiceTTS Demo 音频声效(Audio)之语音识别 音频声效-iOS Mp3 Recorder 音频声效--VoiceTTS Demo 音频声效类--AAC Audio Converter 音频声效类--BobMusic播放器 音频声效类--Groover 音频声效之Hysteria Player 音频声效之Sound Board 游戏引擎类 游戏引擎(cocos2d)Castle Hassle 游戏引擎类》》模仿合金弹头Demo 游戏引擎类--tank大战 游戏引擎类--Tiny Seal 游戏引擎类--基于cocos2d的连连看游戏 游戏引擎类--简单炸弹人小游戏源码 游戏引擎类--切水果游戏 游戏引擎类幸运大转盘的抽奖游戏 游戏引擎-推箱子游戏 游戏引擎之雷电游戏的激光子弹 指示器类 指示器(HUD)之Android Style Toast 指示器-Activity Bar 指示器--Notify HUD 指示器之MBAlertView 指示器之YLActivityIndicatorView 其他类 财付通打印票据和拖动银行卡效果 寸光阴课程表 功能齐全的计算器 每日金句 扫雷游戏 数字输入 天气预报 之DDClock 之滚动视图旋转菜单 状态栏-StatusBar Notifier View 状态栏之MPNotificationView Ad Controller AdMob demo Animation之Gmail Like Loading AWVersionAgent Bee Framework Circle Menu Cocoa Touch Barcodes Contact Picker FileMD5Hash Fontastic Icons fontawesome Harpy iHasApp In-App Feedback In-App Purchase Inner Shadow Layer Keyboard之DLIDEKeyboard Leak Hunter Message Template NSLogger NSUndoManager Demo Open InApp Activity Passcode Passcode Lock Rating Control SBook ScaffoldKit for Core Data Shine Effect SKYdata Sliding Puzzle Board Spotlight System File Browser Ternary Search Tree UIBezierPath Symbol UITextField 焦点提示 Webview之UIWebView 离线浏览
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值