1 Star 0 Fork 0

cyhgyq/testIos

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
testIos_mac之前备份.txt 165.75 KB
一键复制 编辑 原始数据 按行查看 历史
hongyiyi 提交于 2023-11-15 22:21 . 修改
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146
Objective-C ---- Obj-c-----OC-----Swift
OC完全兼容C语言,在OC里面可以写C语言。
打开XCode程序,新建一个工程,基础学习可以在选择OSX-->Command Line Tool, 其中Cocoa Application是带饥饿IM的程序, Language选择Objective-C, 生成后缀.m的文件。
main函数仍然是入口和出口函数
返回int值,return 0;
main参数也是用户传的参数
#import预处理指令, 增强了不管包含多少次,都只会引入一次。对#include进行增强
Foundation框架, 即基础框架
@autoreleasepool 自动释放池,代码可以放里面,或删除
NSLog(@"aaaaa"); 打印函数, NSLog(@"格式控制字符串", 变量列表);
C语言的字符串存储方式:字符数组、字符指针
OC字符串:NSString, 只能存储OC的字符串常量
OC的字符串常量必须使用前缀@符号
"jack" 是C语言的字符串
@"jack" 是OC字符串
NSString *str = @"jack";
NSLog的一个参数是OC的字符串
NSLog(str)
NSLog(@"我是%@", str); 必须用%@占位
@符号的作用:
将C语言字符串转换成OC字符串
OC中的绝大部分关键字都是以@符开头
注释 // /**/
函数跟C语言一样
OC和C程序各个阶段的后缀名对比:
源文件 目标文件 可执行文件
c .c .o .out
OC .m .o .out
OC数据类型:
支持C语言的所有数据类型
基本数据类型 int double float char
构造类型 数组 结构体 枚举
指针类型 int *p1;
空类型 void
typedef自定义类型
BOOL类型: YES本质是1, NO本质是0, 本质是有符号的char类型
Boolean: 存true本质是1或false本质是0, 本质是无符号的char类型
常用 BOOL类型
class类型
id类型,万能指针
nil 与NULL差不多
SEL 方法选择器
block 代码段
OC运算符:支持C的所有运算符
OC控制语句:支持C的所有控制语句"
OC关键字:支持C的所有关键字。
OC语言完全兼容C语言,在OC中可以写任意的C代码,并且效果一致
类的声明:
@interface 类名 : NSObject
{
变量的定义
}
类方法的声明
- (返回值类型) 方法名称;
- (返回值类型) 方法名称:(参数的类型)形参名称;
@end
类的实现:
@implementation 类名
类方法的实现
- (返回值类型) 方法名称
{
}
- (返回值类型) 方法名称:(参数的类型)形参名称{
}
@end
定义对象:
类名 *对象名 = [类名 new];
例子:
#import <Foundation/Foundation.h>
#pragma mark 我是一个人类的声明 //分组导航标记,可以不写, 一般点击分组导航出现这个提示
//#pragma mark - //分组导航出现一个水平分隔线
//#pragma mark - 人类声明 //导航线和声明文字同时产生
@interface Person : NSObject
{
@public //下面的三个属性都会是公有的,
NSString *_name; //属性名必须以下划线开头, 默认外面不允许访问属性,必须加@public才可以访问
int _age;
float _height;
}
- (void)run; //无参数方法的声明
- (void)eatWith:(NSString *)foodName; //带一个参数的方法声明, 一个参数一般用With结尾提高阅读性,如xxxWith或xxxWithXxx,当然也可以直接eat.
- (int)sum:(int)num1 :(int)num2; //带多个参数的方法声明, 多个参数一般用下面一句的写法, with结尾
- (int)sumWith:(int)num1 and:(int)num2; //带多个参数的方法声明二
- (int)sumWithNum1:(int)num1 andNum2:(int)num2; //带多个参数的方法声明三
@end
#pragma mark 我是一个人类的实现 //分组导航标记,可以不写, 一般点击分组导航出现这个提示
@implementation Persion
- (void)run //无参数放到定义
{
_name=@"jack";
}
- (void)eatWith:(NSString *) foodName //带一个参数方法定义
{
}
- (int)sum:(int)num1 :(int)num2 { //带多个参数方法定义 方法1
return num1 + num2;
}
- (int)sumWith:(int)num1 and:(int)num2 { //带多个参数方法定义,推荐写法 方法2
return num1 + num2;
}
- (int)sumWithNum1:(int)num1 andNum2:(int)num2 { //带多个参数方法定义,推荐写法 方法3, andNum2也可以改成toNum2等自己定义。
return num1 + num2;
}
@end
int main(int argc, const char * argv[])
{
Person *p1 = [Person new];
p1->_name = @"jack"; //方式一 常用
(*p1)._name = @"jack"; //方式二 少用
p1->_age = 15;
p1->_height = 170;
NSLog(p1->_name) //%@字符串占位, %d整数占位
[p1 run]; //无参数方法的调用
[p1 eatWith:@"红烧肉"] //带一个参数的方法调用
[p1 sum:10 :20] //带多个参数的方法调用
int sum2 = [p1 sumWith:10 and:20] //带多个参数的方法调用二,推荐
int sum3 = [p1 sumWithNum1:10 andNum2:20] //带多个参数的方法调用三
return 0;
}
类必须有声明和实现
类名首字母大写
初始化对象属性时:基本数据类型默认0, C语言的指针类型默认是null, OC的类指针类型默认是nil
对象中只有属性,而没有方法,自己类的属性外加1个isa指针指向代码段中的类。
NULL等价0,指内存的地址
nil 只能作为指针变量的值,代表不指向内存空间, 也等价0;
NULL和nil本质是一样的。即 nil == NULL; 约定:C指针用NULL, OC的类指针用nil --- int *p1 = NULL; Person *p1 = nil;
对象指针是nil, 访问属性会报错,访问方法不会报错,但也不运行这个方法。
多个指针指向同一个对象:Person *p1 = [Person new]; p1->name=@"jack"; Person *p2 = p1; p2->name=@"tom"; 则p1-name也是@"tom"
点击最上面的文件路径的最后一个字符串,则可以看到这个文件定义了哪些类,叫做分组导航。
类的声明和实现都必须存在,并且声明在实现的前面。而且类声明必须在类使用的前面。
类的属性名必须用下划线开头。
属性不允许声明时候初始化。
如果类方法只有声明,没有实现,不会报错,编译只会有一个警告, 但运行会报错
多文件开发:
.h 头文件
写类的声明
.m 实现文件
写类的实现
Person.h文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
{
@public
NSString *_name;
int _age;
HMDog *_dog;
}
+ (Person *) person; //最好加上,返回纯洁的对象
+ (Person *) personWithName:(NSString *)name andAge:(int)age; //创建不纯洁的对象
- (void)run;
- (void)test:(Dog *)dog; //类可以作为方法参数
- (Dog*)test1; //返回值是一个类
- (Dot*)makeDogWithName:(NString*)name andAge:(int)age andGender:(NString*)gender;
+ (void)run2;
@end
Person.m文件
#import "Person.h"
@implementation Person
+ (Person *) person
{
Person *p1 = [Person new];
return p1;
}
+ (Person *) personWithName:(NSString *)name andAge:(int)age
{
Person *p1 = [Person new];
p1->name = name;
p1->age = age;
return p1;
}
- (void)run
{
}
- (void)test:(Dog *)dog
{
dog->name = @"旺财";
}
- (Dog*)test1
{
Dot *wangCai = [Dog new];
wangCai->name = @"旺财";
return wangcai;
}
- (Dot*)makeDogWithName:(NString*)name andAge:(int)age andGender:(NString*)gender
{
Dog* xiaoqiang = [Dog new];
xiaoqiang->name = name;
xiaoqiang->age = age;
xiaoqiang->gender = gender;
return xiaoqiang;
}
+ (void)run2
{
}
@end
main.m文件
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
Person *p1 = [Person new]
Dog *d1 = [Dog new]
d1->name=@"大黄";
[p1 test:d1] //对象作为方法参数的调用, 地址传递
//d1->name的值是 旺财, 因为是地址传递给方法
Dog *d2 = [p1 test1] //调用返回值是一个对象的函数
Dog *d3 = [p1 makeDogWithName:@"小强" andAge:0 andGender:@"男"] //调用返回值是一个对象的函数
HMDog *wc = [HMDog new];
wc->_name=@"wc";
p1->_dog = wc;
p1->_dog->_name=@"mm";
[p1->_dog shout]; //调用HMDog的方法
[Person run2]; //类方法的调用
Person *p2 = [Person person]; //创建对象的方式二、oc规定最好定义这个类方法
Person *p3 = [Person personWithName:@"jack" andAge:1]; //创建方式三,创建不纯洁的对象, 所以苹果类都有类似的三种创建方式
return 0;
}
右键新建文件,可以新建Header File类型文件,Objective-C File文件,这都是单独新建的。但是新建Cocoa Class类型的文件,会同时生成.h和.m文件
类名可以跟文件名不一致,但最好保持一致
类可以作为方法的参数
XCode开发工具有很多问题,引入文件写错了字母不会报错,跨工程还能提示引入其他工程的文件,等等都是错误的。
如果Foundation框架里面有的类,我们又定义一个同名的类会出问题,最好加上前缀。
异常:
@try
{
}
@catch(NSException *ex)
{
NSLog(@"-----%@----", ex); //大于发生异常的原因, 只要是打印对象都用%@, 比如NString
}
@finally
{
}
@try..@catch不是所有的错误都能抓取到,特别是C语言的错误抓取不到,比如除0错误就抓取不到。OC有效也抓取不到,比如nil访问属性也抓取不到。所以很少用@try...@catch
避免异常最常用的方式还是用if进行逻辑判断
OC方法分为对象/实例方法,类方法 两种:
XCode文件复制:选中文件右键----show in finder---弹宽---在弹框中拖动另外一个文件夹中----勾选Copy Items if needed 和 Create groups
类方法声明使用+
对象/实例方法使用-
对象方法和类方法处理+/-不一样,其他的一样
对象方法的调用:先创建对象,在通过对象名来调用
类方法的调用: 不需要创建对象,直接类名调用, 且不能用对象去调用。
类方法的特点:节约空间,提高效率,因为不需要找对象,而直接找类,再调用方法。
类方法不能直接调用对象方法和属性
在对象方法中能直接调用类方法
对象方法调用对象另外一个方法[self run];
关于类方法的规定:
1. 如果我们写1个类,那么就要求为这个类提供1个和类名同名的类方法。这个方法创建1个最纯洁的对象返回,即属性都是默认值的对象。因为苹果和第三方的类都遵守这个规范。
2. 如果要不纯洁的对象,就应该加参数。
NSString:
ctrl+鼠标点击 -》到定义处。 右键---菜单--进入定义处
NSString *str0 = [NSString new];
NSString *str1 = [NSString string];
上面两种方式创建的字符串都是:@""
NSString *str2 = [NSString stringWithFormat:@"jack"];
NSString *str3 = @"jack";
@"jack"本质是一个NSString对象
NSLog(@"str= %p", str3); //打印对象的地址 %p
NSString的类方法:
stringWithUTF8String 返回 instancetype类, instanceType表示当前这个类的对象。将C语言字符串转OC字符串
char *str0 = "rose";
NSString *str1 = [NSString stringWithUTF8String:str0]; 将C语言字符串转OC字符串
stringWithFormat 返回instancetype, 用于拼接成OC字符串
int age = 19;
NSString *name = @"小明";
NSString *str = [NSString stringWithFormat:@"aa%@,bb%@", name, age];
NSString对象方法:
NSUInteger len = [str length]; //返回OC字符串长度 unsinged long
NSLog(@"len = %lu", len);
unichar ch = [str characterAtIndex:2]; //获得OC字符串指定下标的字符 %C 打印, 不能用%c打印,因为小写的c是一个字节,而unichar是两个字节,所以大写
OC字符串相对不能用 == 来判断, 因为不同方式创建的OC字符串可能不相等。
if([str1 isEqualToString:str2]){} //正确的判断OC字符串是否相对
[str compare:str2]; //判断字符串大写,可以用int接收这个返回的枚举,-1小于,0等于,1大于
匿名对象:
[[Person new] sayHi]; //匿名对象调用sayHi函数
setter:
getter:
@interface Person: NSObject
{
NSString *_name;
int _age;
}
- (void)setAge:(int)age; //对私有属性赋值,set开头,去除下划线
- (int)age; //getter的方法直接去掉下划线, 而不是getAge--注意!!!---必须这样写
@end
@implementation Person
- (void)setAge:(int)age
{
if(age >= 0 && age <= 200)
{
_age = age;
} else
{
_age = 18;
}
}
- (int)age
{
return _age;
}
@end
int main(arc, const char * argv[]) {
Person *p1 = [Person new];
[p1 setAge:56]; //调用setter方法
int age = [p1 age]; //调用getter方法
}
OC约定,只要属性要被外面使用,就必须去掉@public, 并且封装getter和setter
如果属性不需要被外面使用,则不需要封装。 所以我们一般都不需要出现@public这个字符串
只读封装:只有getter封装,没有setter封装
只写封装:只有setter封装,没有getter封装
对象和对象之间的关系:
组合关系:计算机包括cpu、内存、硬盘等。
依赖关系:参数是对象的关系
关联关系:人拥有一条狗,就是关联关系,代码上跟组合关系代码一样,都是对象作为属性。
XCode文档的安装:全是英文的
在线安装:偏好设置---download---很慢不推荐
离线安装: 下载离线zip---解压---拖到安装目录
window---打开文档
C语言中的static关键字:
可以修饰局部变量,可以修饰全局变量,可以修饰函数。
OC的static关键字:
不能修饰属性,也不能修饰方法,
可以修饰方法中的局部变量---静态变量,存在常量区,下次使用方法时,不需要重新声明
如果方法的返回值是当前类的对象,那么方法的返回值就写instancetype
self关键字:
方法内部可以定义跟属性名相同的变量_name,方法内部访问同名的变量是局部变量,如果要访问同名的属性就要用self->_name;
方法调用相同对象的另外一个方法:[self sayHi]; sayHi是对象方法, 对象方法不能用self去调用类方法。
self可以在对象方法和类方法中使用,在对象方法中self执行当前对象, 在类方法中self指向当前类。[self sayHi2]; sayHi2是类方法。 即[Person sayHi2], 推荐用self调用, 类方法self不能方法属性。
取类在代码段中的地址的方式: %p打印
a.调试查看对象的isa指针的值
b. 再类方法中查看self的值
b. [p1 class],即调用对象的对象方法class 就会返回这个对象所属的类在代码段的地址
c. [Person class], 即调用类的类方法class 就会返回这个类在代码段中的地址
对象方法与对象方法不能重名,类方法与类方法不能重名, 对象方法和类方法可以重名。
继承:
@interface Student : Person //继承
{
@private
NSString *_aa;
@public
int _bb;
int _cc;
}
@end
@implementation Student //这个地方不要写 : Person
{
NSString *_dd; //这里放真私有的属性。 且这里不能写访问修饰符
int ee;
}
- (void)sisi //私有方法,只有实现没有声明。
{
}
- (void)sayHi //父类有这个方法,子类重写这个方法,就不是私有的,而是重写方法。不需要声明
{
}
@end
一个类有且只有一个父类;
子类不能存在和父类同名的属性;
super关键字:
可以用在类方法和对象方法中;
对象方法中:可以调用父类的对象方法,[self sayHi]; [super sayHi];这两个效果一样,推荐用第二个。
类方法中:可以调用父类的类方法: [Person hehe];[Student hehe]; [self hehe]; [super hehe]; 效果一样,推荐最后一个, hehe是Person的类方法; 但是在类方法中super不能访问父类的属性。
访问修饰符:
@private: 只能在本类的内部访问,即只能在本类的对象方法中访问。 私有的属性子类也有,只是子类不能访问。
@protected: 只能在本类和本类的子类访问。
@package: 可以在当前框架中访问
@public: 可以在任意地方访问
没有修饰符,默认@protected
修饰符下面的属性,一直到下一个修饰符前面的属性,都属于这个修饰符的内容。
使用建议:@public 无论什么时候都不要使用。 推荐使用@protected,即不写任何修饰符
访问修饰符只能修饰属性,不能修饰方法。
在@interface里面设置的@private私有是假私有,xcode在类外面还会有提示,只是有红色删除线,不能使用。在@implementation放属性是真私有,xcode不会有任何提示。 区别只有xcode提示。推荐在@interface
私有方法:只写实现不写声明就是私有方法。 只有在类内部访问。
多态:
Person *p1 = [Student new]; //多态, 如果一个方法的参数是父类对象,则即可以传父类对象也可以传子类对象。
//不能调用子类独有的方法和属性,只能调用父类的方法和成员。
方法重写:
子类重写父类的方法;子类只要写实现,不需要写声明。
Person *p1 = [Student new];
[p1 sayHi]; //父类指向子类,调用重写方法,调用的是子类的重写方法。 如果子类没有重写,就调用父类的。
NSObject类:
所有类都继承NSObject类。所以所有的类都要直接的或者间接的从NSObject继承: 即: @interface Person : NSObject
new、isa都是NSObject的方法和属性
如果用%@打印1个对象, 输出格式是:<对象所属的类名:对象的地址>, 原因是:会调用对象的description方法,拿到返回值,再打印这个返回值。这个方法定义在NSObject中。
如果要修改打印格式,需要重写description方法就行:
- (NSString*) description
{
return [NSString stringWithFormat:@"姓名:%@ 年龄%d", _name, _age];
}
子类的isa属性会指向父类的代码段。依次类推最终知道NSObjecct. 所以子类对象可以调用父类的方法。
如果传enum枚举做参数,不需要加*
结构体和类的相同点:
都可以将多个数据封装为1个整体。
结构体和类的不同点:
结构体只能封装数据, 类即可以封装数据也可以封装行为。
结构体的变量分配在栈空间, 类的属性分配在堆空间。
占空间小,访问效率高,堆空间大,访问效率小。
应用场景:有属性和方法时只能使用类。
如果表示的实体没有方法时:如果属性较少可以用结构体, 如果属性很多最好用类。 因为栈空间占用太多影响效率。
结构体相互赋值是值赋值, 类之间相互赋值是地址赋值。
结构体作为方法的参数不需要加*, 即值传递。
(Date){1998, 12, 12} 如果Date是结构体, 这样表示类型强转。
类的本质:
内存:栈、堆、BSS段(未初始化的全局变量和静态变量,一旦初始化后就回收,并转存到数据段)、数据段(初始化的全局变量和静态变量,程序结束才回收)、代码段(存储代码,程序结束才回收)。
第一次访问类,就进入代码段, 直到程序结束,代码段才会销毁。
类在代码段是一个class对象, 这个对象也有isa属性,指向父类的class对象。 class对象里面存了类的名字类的属性标志类的方法标志等。
如何拿到存储在代码段中的类对象:方法1:Class c1 = [Person class]; 其中c1不要加*, c1对象就是Person类,c1完全等于Person类
方法2: Person *p1 = [Person new]; Class c2 = [p1 class];
如何使用代码段中的类对象: [c1 sayHi]; sayHi是类方法, 类对象调用类方法,相当于;[Person sayHi];
[c1 new] 相当于 [Person new];
SEL : 是一个selector选择器, 是一个数据类型,是一个类,是用来存储1个方法的。
代码段里面的每个方法都是一个SEL对象, 这个方法对象存储在代码段的类对象的属性中。
因为SEL是一个typedef类型,在定义的时候已经加*了,所以声明SEL指针的时候,不需要加*
SEL s1 = @selector(sayHi); //取到存储方法的SEL对象。
NSLog(@"s1 = %p", s1);
调用方法的本质是先拿到方法的SEL对象,在将SEL消息发送到p1对象,再调用这个方法。
[p1 performSelector:s1]; 相当于:[p1, sayHi];
有1个参数的SEL调用,获取时必须要有冒号
SEL s2 = @selector(sayHi:); //必须要有冒号, 多个参也是这样
[p1 performSelector:s2 withObject:@"haha"]; //1个参数
[p1 performSelector:s2 withObject:@"haha" withObject:@"aaa"]; //两个参数, 这种写法最大只能有两个参数,如果有多个参数时,应该定义成类。
多个参数的函数,应该将函数的参数合并成一个类。这一SEL就调用1个参数就好了。
点语法:
语法:对象名.去掉下划线的属性名;
本质是调用对象的getter和setter方法,所以必须要有这两个方法才能用点语法。
后面常用点语法。
getter和setter里面最后不要用self.age, 即这两种方法里面最好不要用点语法,因为可能会循环调用导致死循环。 如setter方法中调用self.name=@"haha"; 这一句又会调用setter方法导致死循环。
点语法在编译时候就会转换成setter和getter方法。
Person *p1 = [Person new];
p1.name = @"jack";
NSString *name = p1.name;
@property
写一个类,先写属性,在实现getter和setter方法, 写起来太繁琐,使用@property自动生成getter和setter方法的声明。实现还要自己实现。
@property的类型和属性的类型一致,名称是属性的名称去掉下划线。
@interface Person : NSObject
{
NSString *_name;
int _age;
float _height;
float _weight;
}
@property int age;
@property NSString *name;
@property float height, weight; //批量声明,前提条件是类型要一样。
@end;
@synthesize:
自动生成getter和setter的实现, 一定要是@property声明的属性, 并且名字和属性要一致
会自动生成一个真私有的属性,类型和名字跟@synthesize一致。
自动生成setter方法的实现,并且参数直接赋给自动生成的真私有的属性,
自动生成getter放的实现,并且返回自动生成的真私有的属性的值。
所以自己定义的属性一直不会变和访问, 后续操作的都是自动生成的真私有属性。
问题:类的属性会翻倍, 直接删除大括号自定义的属性,也能将程序正常运行起来
我们希望@synthesize不要自动生成真私有属性,而是用我们自己定义的属性。 语法:@synthesize age = _age; 进行赋值就可以达到效果。
@implementation Person
//@synthesize name; 不推荐, 会生成真私有属性
//@synthesize age; 不推荐, 会生成真私有属性
@synthesize name = _name; //推荐, 不会生成真私有属性
@synthesize age = _age; //推荐, 不会生成真私有属性
@synthesize weight = _weight, height = _height; //批量实现, 类型不一致也可以这样写。
@end;
上面的@property和@synthesize的写法是Xcode4.4之前的写法,之后对@property进行增强
@property在Xcode4.4之后,增强如下:
自动生成私有属性,自动生成getter setter声明和getter setter实现
@property NSString *name;
自动生成一个私有属性和getter setter的声明和实现,类型和名称一致, 属性的名称自动的加1个下划线;
所以不需要大括号定义_name属性,
Xcode4.4以后,只要这一句代码就完全实现了上面大括号属性、@property、@synthesize这三个功能;
并且会自动生成_name私有属性,而不会自动生成name的真私有属性。
批量声明如下:@property float width, height;
如果要自己实现setter逻辑,只要重写setter方法就可以了,里面写入自己的逻辑就行, 会自动覆盖@property生成的setter实现方法。
setter和getter只能重写一个,如果两个都重写,会报错,如果两个都重写就不要用@property方式。
自动生成的setter和getter方法会被继承到子类。
OC是1门弱语言,即编译器检查的时候不严格,如字符串可以赋值给任何一个类。不同类型相互赋值编译器不会报错。运行时会报错。
指针指向本类的称静态类型, 指针指向子类型的称动态类型。
类型强转:(Dog *)p1;
子类对象传给父类指针,如果要调用子类对象独有的方法,就必须类型强转再调用方法。
id指针:
是一个万能指针,可以指向任意的OC对象。是一个typedef自定义类型,定义的时候已经加了*,类似NSObject*
NSObject与id指针的区别:
相同点:都是万能指针,都可以指向任何OC对象
不同点:编译时,NSObject会做类型检查,id指针不会做类型检查,如都指向Person对象, [obj sayHi]; //会有警告, [id1, sayHi]; //不会有警告, 但运行时,这两个都会报错。
id指针只能调用方法,不能调用属性,且不能使用点语法。
总结:如果要定义万能指针,最好用id指针,因为它使用方便。
id id1 = [Person new];
类方法也是可以继承的:
方式一:如果:声明:+ (Person*) person;
实现: + (Person*) person
{
return [Person new];
}
则:
Person *p1 = [Person person];
Person *p2 = [Student person]; //用person返回,子类返回的还是Person,person是新建类对象的类方法。
方式二:如果:声明:+ (id) person; //用这种方式有一个问题,person可以赋值给NSString类型,还不会有警告
实现:+ (id) person
{
return [self new]; //如果子类继承,返回的子类对象
}
则:
Person *p1 = [Person person];
Student *s1 = [Student person]; //用id返回,就是子类对象
方式三:如果:声明:+ (instancetype) person; //推荐这种方式,返回instancetype限制了返回类型,这样person就不能赋值给NSString类型了。
实现:+ (instancetype) person
{
return [self new]; //如果子类继承,返回的子类对象
}
则:
Person *p1 = [Person person];
Student *s1 = [Student person]; //用id返回,就是子类对象
使用建议:如果方法内部是在创建当前类的对象,不要写死,用[self new]; 返回类型instancetype.
id和instancetype的区别:instancetype只能用于方法的返回值,id可以作为变量、参数、返回值。
instancetype是有类型的,类型是当前对象, id是一个无类型的指针,可以是任何值。
XCode的编译器是LLVM, 可以编译C\C++\OC等语言。
动态类型检查:
BOOL b1 = [p1 respondsToSelector:@selector(sayHi)]; //判断这个对象是否能响应这个函数,可以避免运行时报错。即是否有对象方法
if(b1 == YES)
{
}
else
{
}
BOOL b2 = [p1 isKindOfClass:[Person class]]; //判断p1是否是Person类型对象或者Person的子类对象
BOOL b3 = [p1 isMemberOfClass:[Person class]]; //判断p1是否是Person类型对象, 但不包括Person的子类对象。
BOOL b4 = [p1 isSubclassOfClass:[Person class]]; //判断p1是否是Person的子类对象
BOOL b5 = [p1 instancesRespondToSelector:@selector(sayHi)] ; 判断是否存在sayHi这个类方法
new方法内部是先调用alloc方法,再调用init方法, alloc是个类方法,生成对象,init是对象方法,初始化对象。
Person *p1 = [Person new] 相当于 Person *p1 = [[Person new] init];
init方法默认基本类型赋值0, C指针赋值NULL, OC对象赋值nil;
重写init构造方法:可以初始化属性不要默认值时,要重写init方法。
必须先调用父类的init方法并赋值给self;
调用init初始化对象有可能失败,失败返回nil
@implementation Person
- (instancetype)init
{
self = [super init];
if(self != nil)
{
self.name=@"jack";
}
return self;
}
@end
自定义构造方法:因为重写init初始化的值时固定的,所有要用自定义构造方法:
返回值必须是instancetype,
方法名称必须以initWith开头,
其他的跟重写init方法一样。
声明处书写:上面的init方法时不需要声明只要实现
- (instancetype)initWithName:(NSString *)name andAge:(int)age;
实现处书写:
- (instancetype)initWithName:(NSString *)name andAge:(int)age
{
if((self = [super init]))
{
self.name = name;
self.age = age;
}
return self;
}
调用方式不同:Dog *d1 = [[Dog alloc] initWithName:@"小黄" andAge:2];
内存管理:
堆:OC对象,C语言申请的动态空间。
堆内存要自己管理,其他地方的内存系统自己管理。
每个对象有一个属性retainCount, 叫做引用计数器,默认0, 类型是unsigned long 的8字节, 默认1,记录多少个对象在用, 多一个对象在用,计数加1,少一个对象用就减1.
原理:发送retain消息,计数加1,release消息,计数减1,retainCount消息获得计数器的值,当计数为0,会自动调用dealloc方法回收堆内存。
内存管理分类:MRC:手动内存管理,即上面的那几个消息要自己发送。--面试必考
ARC: 自动内存管理, 系统自动发送那几个消息 --- 推荐
Xcode4.2开始支持ARC
Xcode7 默认支持ARC开发, 如果要使用MRC开发,必须关闭Xcode的ARC, 再开发,关闭方法自己百度。
MRC的使用:
重新dealloc方法,监控回收的时机
- (void)dealoc
{
NSLog(@"回收了");
[super dealoc]; //最后一句要调用父类的方法。
}
测试:
Person *p1 = [Person new];
NSUInteger count = [p1 retainCount]; //获得引用计数 , 或者p1.retainCount也可以获得计数
打印count
[p1 retain]; //计数加1
[p1 release]; //计数减1
重点:多1个人使用这个对象时,发送retain消息,少1个人使用这个对象时,发送release消息。
在ARC的XCode下,无法调用上面的消息,必须先关闭。
原则:创建一个对象, 就要匹配1个release, retain了一次,就要release一次。
谁用谁retain, 谁不用谁release.
只有多一个人使用才retain,少一个人使用才release.
野指针和僵尸对象:
C语言:没有初始化的指针
OC语言:对象释放了的指针。
[p1 release]; 计数为0时,马上回收,不会延时回收。回收后,空间还没有分配给别人,用p1方法属性还能访问到正确值,这种情况是僵尸对象,但如果这块空间分配给别人后,访问就乱了。
野指针访问僵尸对象时,可能得到正确值,也可能得到错误值。
希望访问僵尸对象就报错:XCode打开访问僵尸对象报错机制,百度一下怎么打开,默认关闭。打开后,极其消耗性能。
所以:计数为0时:让 p1 = nil; 这样可以防止野指针,但是还访问属性不会报错。
[p1 retain]; 无法复活僵尸对象。
MRC模式下内存泄漏:应该回收的内存但没有回收。
泄漏情况:创建对象没有release
retain了却没有release
p1 = nil; 计数为0前调用这一句也会内存泄漏。
方法参数对象,不要不适当的调用retain和release.
MRC模式对象的某个属性是OC对象的总结写法:
- (void)setCar:(Car*)car
{
if(_car != car)
{
[_car release];
_car = [car retain];
}
}
- (void)dealloc
{
[_car release];
[super delloc];
}
@property的参:
MRC中@property如果不带参数就不能用了:
@property(参数1,参数2,参数3,...);
a. 与多线程相关的两个参数:atomic(默认:线程安全)、nonatomic(线程不安全): @property(atomic) Car *car; 非多线程推荐用nonatomic
b. 与生成的setter方法的实现相关的参数:assign(默认:直接赋值)、retain(标准的MRC内存管理代码--只能用于MRC机制下): @property(atomic, retain) Car *car; OC对象推荐用retain但仍需要实现dealloc方法,基础类型用assign。两个OC对象相互引用,一个用retain,一个用assign
c. 与生成只读、读写相关的参数: readonly(指生成getter方法), readwrite(默认:getter和setter方法都会生成)
d. 是与生成的getter setter方法名字相关的参数:指定set和get的名字:getter setter: @property(atomic, retain, getter=getMyCar, setter=setMyCar:) Car *car; 用来改自动生成的方法的名字。注:setter的名字要带冒号,表示带参数
setter参数不管什么时候都不能去改名字,getter参数为了可读性如BOOL类型时可以考虑改一下名字。
e. strong:默认都是强引用, week:弱引用, 如果两个对象相互引用,则一个用强引用,一个用弱引用。-----只能用在ARC机制下。
@class:
Person类中有Book类的属性:@import "Book.h"; Book类中有Persond的类的属性:@import "Person.h", 相互引用会报错,可以在其中一个改成@class,不用双引号,就不报错:@class Book;
并且一端要用retain,一端要用assign
#import是将指定的文件的内容拷贝到写指令的地方
#class并不会拷贝任何内容,只是告诉编译器,这是一个类,这样编译器在编译的时候才可以知道这是1个类。但是写代码不会有提示
ARC内存管理:
自动释放池:不需要手动调用release,自动调用所有的对象的release.
创建自动释放出:@autoreleasepool
int main(int argc, const char* arg[]) {
@autoreleasepool
{
Person *p1 = [Person new];
[p1 autorelease]; //将p1对象放入自动释放池, 只有调用这一句代码,后续销毁对象会自动调用release
或者: Person *p1 = [[Person new] autorelease];
或者 Person *p1 = [[Person alloc] init] autorelease];
}
}
注意: 唯一的作用是可以省略创建对象时匹配的release方法,而其他的release方法无法省略。
1.一定要调用autorelease方法
2. 对象创建可以在@autoreleasepool外面,但是autorelease方法的调用一定要在@autoreleasepool里面
3. @autoreleasepool的大括号结束时,表示释放,即仅仅自动调用到了[p1 release]; 方法。
4.[p1 autorelease];如果调用多次,表示存了三次,释放的时候也会释放三次。这样会出现僵尸对象错误。
5.@autoreleasepool里面最好不要调用retain和release方法。如果有要成对出现
如果Pig是一个类,构建函数带参数:initWithName, pigWithName;
如果在pigWithName里面调用autorelease,那么就可以省略在@autoreleasepool中调用autorelease.
- (instancetype)initWithName:(NSString *)name andAge:(int)age andWeight:(float)weight
{
if(self = [super init])
{
_name = name;
_age = age;
_weight = weight;
}
return self;
}
+ (instancetype)pigWithName:(NSString *)name andAge:(int)age andWeight:(float)weight
{
return [[[self alloc] initWithName:name andAge:age andWeight:weight] autorelease]; //---很多情况是在这里调用autorelease方法。
}
- (void)dealloc
{
NSLog(@"猪销毁了");
[_name release];
[super dealloc];
}
约定:使用init开头创建对象,需要autorelase:
Pig *p1 = [[Pig alloc] initWithName:@"aa" andAge:2 andWeight:3f];
[p1 autorelease];
NSString *str0 = [[NSString alloc] initWithFormat:@"jack"];
[str0 autorelease];
Pig *p2 = [Pig new];
[p2 autorelease];
使用类名开头创建的对象,不需要autorelease
Pig *p1 = [Pig pigWithName:@"aa" andAge:2 andWeight:3f];
NSString *str1 = [NSString stringWithFormat:@"jack"];
什么是ARC: 跟平常一样写代码,不需要去写retain、release、autorelease, dealloc这四个关键字。从ios5开始才有ARC.
是编译器会在合适的地方,加上面四个关键字。
__weak Person *p; 表示弱指针;不能直接创建一个对象赋值给弱指针,否则马上会回收掉,应该将一个强指针赋值给弱指针。
__strong Person *p; 表示强指针,可以省略__strong;
在ARC的机制下,当1个对象没有任何的强指针指向它的时候,这个对象就会被立即回收。
其实在ARC的机制下,加@autoreleasepool没有什么作用。只能限制一个大括号内的对象为nil时去回收内存。
ARC机制下实现dealloc方法,里面不能有relase调用,也不能有supper,应该值用于打印:
- (void)dealloc
{
NSLog(@"释放了");
}
ARC机制下,@property中的参数不能用retain.
@property(nonatomic, week)Person *person; //表示声明的是弱指针, 默认是强指针,或者显式的将week改成strong.
如果Person对象中有Book对象, Book对象中也有Person对象,两个对象相互引用,这时就必须在其中一个对象里面的声明要用week.否则内存会永远无法释放。
问题:我们使用ARC开发的,而某些第三方类使用的是MRC开发的,直接集合进来会报错,怎么办:
方式一:点击项目---tarcets---module---Build params---Complle Sourcess----双击MRC的文件---输入:-fno-objc-arc ---- 可以指定某些文件用MRC来编译。---推荐
方式二: 选中项目---edit---Convert----ToObjective-C ARC----会自动将MRC代码转换为ARC代码---不推荐,经常会出错
category--分类:类中有很多方法,放到一块,很臃肿,所以要分类。
解决方法:一个类分成多个模块, 将功能相似的方法放到同一模块中。
新建文件---Objective-C文件---文件类型选择Category----Class选择要分的类比如Student--名字:aa ---next---生成两个文件:Student+aa.h, Student+aa.m; 还有以前存在的文件:Student.h,Student.m.
注意:
分类中只能加方法,不能加属性
分类中可以写@property, 但是不会自动生成私有属性,也不会自动生成getter和setter的实现,只会生成getter和setter的声明,所有不推荐在类里面写@property
分类中的方法不可以直接访问本类的真私有属性(即定义在本类的@implementation中的属性),但可以调用getter和setter来访问属性。普通的私有属性是可以在分类中访问的。即不能访问_name,可以访问name;
分类中有和本类中同名的方法时,优先调用分类的方法,即使没有引入分类的头文件。多个分类有相同的方法,优先调用最后编译的分类。
什么时候使用分类:方法很多很杂时才用分类,或者给系统或第三方的类添加分类来扩展这些类的方法。
分类也可以放静态方法。
Student+aa.h文件内容:
#import "Student.h"
@interface Student(aa)
- (void)hehe;
@end
Student+aa.m文件内容:
#import "Student+aa.h"
@iimplementation Student(aa)
- (void)hehe
{
NSLog(@"hehe");
}
@end
main.m文件的使用:
#import <Foundation/Foundation.h>
#import "Student.h"
#import "Student+aa.h"
int main(int argc, const char * argv[]) {
Student *s1 = [Student new];
[s1 hehe];
return 0;
}
非正式协议:为系统类写分类,如为NSObject,NSString写分类。
如给NSObject创建分类,加了一个run方法,则所有的类都会有run方法了。
Java代码的垃圾回收机制是GC,运行时回收内存,一直在扫描没有引用的对象,然后回收。
OC代码的ARC机制垃圾回收机制是编译器插入retain和release代码,实现代码的回收,编译器控制回收内存代码,效率比GC方式更高,因为不需要循环的扫描内存对象。
分类和继承的区别:分类在原类上添加功能,继承是一个新的类,分类不能扩展属性,继承可以扩展属性。
延展:Extension:
是一个特殊的分类,新建类文件时,可以选择category或Extension.
延展这个特殊的分类且没有名字
延展里面可以加属性
延展里面可以使用@proerty, 并且会自动生成私有属性,也会自动生成setter和getter方法
只有声明没有实现,和本类共享一个实现, 所以实现在本类中。
新建文件---Objective-C File----FileType选择:Extension--Class填写Person---文件名填写aa --- 确定
只会生成Person_aa.h文件,内容如下:
#import "Person.h"
@interface Person()
{
float _height;
}
@property(nonatomic assign)float weight;
- (void)run;
@end
实现在本类Person.m中实现:
#import "Person.h"
#import "Person_aa.h"
@implementation Person
- (void) run
{
NSLog(@"跑步");
}
@end
main.m文件的使用:
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person_aa.h"
int main(int argc, const char * argv[]) {
Person *p1 = [Person new];
[p1 run];
return 0;
}
延展的一个重要作用:将延展声明在本类的实现的同一文件中,来实现用@property声明的属性getter和setter方法禁止在本类外面使用。
其实,我们可以想:@property生成私有属性、生成getter、setter的实现,不要声明。
延展100%的情况下不会独占1个文件, 都和本类实现在同一个文件中。
私有属性一般放到延展中,一般放到@implementation中。
私有方法就是只有实现没有声明的方法,则这个方法只能在类内部使用。因为OC只能引入.h文件,而不能引入.m文件。 但一般不这样做,一般将方法的声明放到延展中,实现在本类中,这样的方法也是私有的。
#import "Student.h"
@interface Student()
{
NSString *_name; //这个属性也不能在本类外面使用
}
@property(nonatomic, assign)int age;
- (void) run;
@end
@implementation Student
- (void) run{
}
@end
上面这些代码在同一个文件中。
block: 是一个OC的数据类型
专门用来存储一段代码,这段代码可以有参数和返回值。
在声明block变量的时候,必须要指定这个block变量存储的代码段是否有参数,是否有返回值。
语法格式:返回值类型 (^block变量的名字)(参数列表);
void (^myBlock)();
int (^myBlock2)();
int (^myBlock3)(int num1, int num2);
初始化block变量:语法如下:
^返回值类型(参数列表){
代码段;
};
^void(){
NSLog(@"呵呵")
}
执行block语法:block变量名();
main.m文件如下:
int main(int argc, const char * argv[])
{
void (^myBlock1)();
myBlock1 = ^void(){
NSLog(@"hehe");
}
myBlock1();
//声明同时初始化:
void (^myBlock2)() = ^void(){
NSLog(@"hehe");
}
myBlock2();
//没有返回值,简写可以去除初始化的void
void (^myBlock22)() = ^(){
NSLog(@"hehe");
}
myBlock22();
//即没有参数又没有返回值简写如下
void (^myBlock222)() = ^{
NSLog(@"hehe");
}
myBlock222();
int (^myBlock3)() = ^int(){
int num = 10;
return num;
}
int sum3 = myBlock3();
//如果没有参数,简写形式可以去掉初始化的小括号
int (^myBlock33)() = ^int{
int num = 10;
return num;
}
int sum33 = myBlock33();
int (^myBlock4)(int num1, int num2) = ^int(int num1, int num2){
int num3 = num1 + num2;
return num3;
}
int sum4 = myBlock4(10, 20);
//简写,声明的地方的参数可以只写类型,不写名字
int (^myBlock44)(int, int) = ^int(int num1, int num2){
int num3 = num1 + num2;
return num3;
}
int sum44 = myBlock44(10, 20);
//任何情况下都可以省略初始化的返回值
int (^myBlock444)(int, int) = (int num1, int num2){
int num3 = num1 + num2;
return num3;
}
int sum444 = myBlock444(10, 20);
//建议不要简写block, 用最标准的方式去写。
return 0;
}
typedef:取别名:将一个名字长的类型取别名变成名字短的类型。
typedef unsigned long long int ulli; //ulli就变成了一个类型的别名。
ulli i1 = 10;
对block类型取别名:
typedef void (^NewType)(); //则NewType就是block类型的别名:
NewType block1;
NewType block2;
typedef int (^NewType2)(int num1, int num2);
NewType2 t1 = ^int(int num2, int num2){
int num3 = num1 + num2;
return num3;
}
block内部访问外部的变量:
在block代码块内部可以取定义在外部的局部变量和全局变量的值。全局变量指在函数外部的值。
在block代码块内部可以修改全局变量的值,但是不能修改定义在外部的局部变量的值。如果希望block内部修改外部的局部变量的值,加上__block定义:__block int num2 = 200;
block的作用:作为函数的参数和方法的参数,或者作为函数和方法的返回值。
常用于将block传给第三方的类,我们传的block就是我们需要处理的过程:如打印、存数据库等等操作。
void test(void (^block1)())
{
block1();
}
或者:
typedef void (^NewType)();
void test(NewType block1)
{
block1();
}
//block作为返回值, 作为返回值必须使用typedef的NewType,否则会报错
NewType test2()
{ void (^block1)() = ^{
NSLog(@"啊啊啊");
}
return block1;
}
int main(int argc, String arg[])
{
NewType type = ^{
NSLog(@"haha");
}
test(type);
test(^{
NSLog(@"haha");
})
//返回类型是block的调用
NewType t = test2();
t();
}
block与函数:
相同点:都封装一段代码
不同点: block是数据类型
block可以作为类型变量,函数的参数和返回值
protocal: 协议:新建objective-c文件时,会有一个下拉选项是否选protocal, 类似前面的分类和延展。
作用:专门用来声明一大堆方法(不能声明属性,也不能实现方法,只能用于写方法的声明)
只要某个类遵守了这个协议,就相当于拥有这个协议中的所有的方法声明。
语法:
@protocol 协议名称 <NSObject>
方法声明
@end
新建文件---文件类型:Protocal---文件名:Aaa---Next:内容如下:
Aaa.h文件的内容如下:
@import <Foundation/Foundation.h>
@protocol Aaa <NSObject>
- (void)run;
- (void)sleep;
@required
- (void)test1;
@optional
- (void)test2;
@end;
类遵守这个协议语法:
@interface 类名: 父类名 <协议名称>
@end
Dog.h文件内容:
#import <Foundation/Foundation.h>
#import "Aaa.h"
@interface Dog : NSObject <Aaa>
@end
Dog.m文件内容:
#import "Dog.h"
@implementation Dog
- (void)run
{
NSLog(@"run");
}
- (void)sleep
{
NSLog(@"sleep");
}
@end
类是单继承,协议是多继承。
@interface Dog: NSObject <Aaa, Bbb> //协议的多继承, 不实现不会报错,但调用没有实现的方法才会报错。
@end;
@required和@optional是用来修饰协议中的方法
@required声明的方法如果没有实现,会报警告,---默认就是@required,
@optional声明的方法如果没有实现, 不会报警告
上面两个修饰的方法,调用没有实现的方法,都会报错。
协议之间可以继承:子协议不仅有自己的方法声明也有父协议的方法声明。
语法:
@protocol 协议名称 <父协议的名称>
@end
比如:
#import "Aaa.h"
@protocol Bbb <Aaa>
@end
在Foundation框架中,有1个类,叫做NSObject, 是所有OC类的基类。
在Foundation框架中,有1个协议,叫做NSObject.
NSObject协议被NSObject类遵守,所以,NSObject协议中的所有方法,全部的OC类都拥有。
所以NSObject协议是基协议,所以其他任何协议都应该直接或间接的继承NSObject协议。
协议类型的限制:
NSObject<Aaa> *obj = @"jack"; //报错,表示指向的是一个NSObject类型, 但是这个类型必须符合Aaa协议。
NSObject<Aaa> *obj = [Dog new]; //不报错
或者: id<Aaa> id1 = [Dog new]; //不报错。
或者@property(nonatomic, assign) NSObject<Aaa> *obj;
或者@property(nonatomic, assign) id<Aaa> id1 *obj
NSObject<Aaa, Bbb> *obj = [Dog new]; //指向同时要遵守两个协议的类。
或者: id<Aaa, Bbb> id1 = [Dog new];
[id1 run];
遵守了这些协议的对象,才能调用这些协议的方法。
Foundation框架:
NSString类:
[NSString new];[[NSString alloc] init];[NSString string];这三种方式创建的默认是@"";
%p打印指针地址
%@打印OC对象
NSString的恒定性:@""方式创建的字符串,存储在常量区(数据段区),不能改变,重新赋值是重新申请了一个字符串的地址,指针地址改变了。[NSString new]创建的字符串是在堆区,内容也不变,新内容是新地址,指针地址一样改变了。
NSString常用方法:
拼接:int age = 10; NSString *str = @"jack"; NSString *str2 = [NSString stringWithFormat:@"aa%@, bb%d",str, age];
长度:int len = str.length;
下标字符:unichar ch = [str characterAtIndex:2]; NSLog(@"ch=%C", ch);
判断两字符串内容是否相同: 不能用==,Bool isE = [str1 isEqualToString:str2];
c字符串转OC字符串:char *str = "aa"; NSString *str1 = [NSString stringWithUTF8String:str];
OC字符串转c字符串:NSString *str = @"jack"; const char *str2 = str.UTF8String;
写入文件:
NSError *err;
BOOL res = [str writeToFile:@"/Users/cyh/Desktop/abc.txt" atomically:NO encoding:NSUTF8StringEncoding error:&err]
if(res == YES){}else{NSLog(@"%@------%@", err, err.localizedDescription)}
参数1:文件路径
参数2:YES--先写入到临时文件,写入成功,在搬到参数1的文件
NO---直接写入到参数1的文件
推荐使用NO.
参数3:编码方式
参数4:传一个NSSError地址, 即二级指针, 写入成功时nil,写入失败,指向失败对象的地址。 如果不想知道错误原因,直接传nil.
返回值Bool表示是否写入成功
读文件:
NSString *str = [NSString stringWithContentsOfFile:@"/Users/cyh/Desktop/abc.txt" encoding:NSUTF8StringEncoding error:&err]
if(err != nil) {NSLog(@"读取失败");}else{NSLog(@"%@", str);}
使用URL读取字符串:既可以读取本地磁盘文件也可以读取服务器网页文件
NSURL *ul = [NSURl URLWithString:@"file:///Users/cyh/Desktop/abc.txt"];
NSString *str = [NSString stringWithContentsOfURL:ul encoding:NSUTF8StringEncoding error:&err];
使用url写:
BOOL res = [str writeToURL:ul atomically:NO encoding:NSUTF8StringEncoding error:&err];
字符串比较:
NSComparisonResult res = [str1 compare:str2];
if(res == NSOrderedAscending){NSLog(@"str1大");}else if(res == NSOrderedSame){} else if(res == NSOrderedDscending){NSLog(@"str1小");}
NSComparisonResult res = [str1 compare:str2 options:NSCaseInsensitiveSearch]; //忽略大小写的比较。
字符串开始和结束判断:
BOOL res = [str hasPrefix:@"http://"]; 是否以http://开头
BOOL res = [str hasSuffix:@".mp3"]; 是否以.mp3结尾。
搜索子字符串下标:
NSRange range = [str rangeOfString:@"love"]; //返回一个结构体, 从前往后搜索
NSRange range = [str rangeOfString:@"love" options:NSBackwardsSearch]; 从后往前搜索
range.location;第一次出现的下标, 用%lu打印
range.length; 子串的长度, 用%lu打印
if(range.length == 0) {NSLog(@"没有找到子串")}
if(range.location == NSNotFound) {NSLog(@"没有找到子串")}
字符串截取:
NSString *newStr = [str substringFromIndex:3]; 从下标为3开始截取
NSString *newStr = [str substringToIndex:3]; //截取前面的三个字符
NSString *newStr = [str substringWithRange:NSMakeRange(2, 3)]; //从下标2开始截取,截取3个字符。
字符串替换:
NSString *newStrin = [str stringByReplacingOccurrencesOfString:@"aa" withString:@"bb")]; //将aa替换成bb , 会全部aa替换。
字符串转其他类型:
int num = str.intValue;
去除前后空格:
NSString *newStr = [str stringByTrimmingCharactersInSet: [NSCahracterSet whitespaceCharacterSet]];
变大写:
NSString *newStr = [str uppercaseString];
变小写:
NSString *newsStr = [str lowercaseString];
NSMutableString:
for循环里面对字符串处理,会创建很多个字符串对象,所以要用NSMutableString, 这个里面不会创建新的NSString对象。 大批量的字符串拼接,应该用NSMutableString. 一般10次以上就应该改用NSMutableString
继承自NSString
字符串可修改而不会创建新的对象。
NSMutableString *str = [NSMutableString string]; //不能 str = @"jack"这样创建
[str appendString@"hello "]; //追加字符串
[str appendString@"jack"];
int age = 10;
[str appendFormat:@"aa@d", age];
NSArray数组:
只能存储OC对象
长度固定,无法新增,也无法删除元素。
每个元素都紧密相连,每个元素有自己的下标
元素类型是id类型
NSArray *arr1 = [NSArray new];
NSArray *arr1 = [[NSArray alloc] init];
NSArray *arr1 = [NSArray array];
上面三个创建的都是长度为0的数组。这样创建没有意义。
NSArray *arr = [NSArray arrayWithObject:@"jack"]; //含有一个元素的数组, 也没有意义
NSArray *arr = [NSArray arrayWithObjects:@"jack", @"Tom", nil]; //多个元素,最后一个一定要加nil, 并且除最后一个其他的不能是nil--常用。
Person *p = [Person new];
NSArray *arr = [NSArray arrayWithObjects:p, p1, nil]; //存储Person对象
NSArray *arr = @[@"jack", @"Tom"]; //上面那句的简写方式。这种方式最后不要加nil. ---常用
NSArray *arr = @[p, p1];
NSLog(@"%@", arr); //打印数组
nil的本质就是0, 所以不能存储到NSArray中。
NSString *str = arr[0]; 取数据
NSString *str = [arr objectAtIndex:0]; 取数据
int len = arr.count; //元素的个数
int len = [arr count]; //获取元素的个数
BOOL res = [arr containsObject:@"jack"];判断是否包含这个元素: %d来打印BOOL, 1表示有,0表示没有
arr.firstObject; //取第一个元素, 跟arr[0]区别,空数组时,arr[0]报错,但是firstObject不报错,返回nil;
arr.lastObject; //去最后一个元素
[arr indexOfObject:@"Tom"]; //第一次出现的下标, 如果没有找到返回NSNotFound
数组遍历:
for(int i = 0; i < arr.count; i++)
{
NSLog(@"%@", arr[i]);
}
for(NSString *str in arr)
{
NSLog(@"%@", str);
}
for(id item in arr) //如果arr里面存储的类型不一致,用id类型。
{
NSLog(@"%@", str);
}
//使用block来遍历:
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonull stop) {
NSLog(@"%@", obj); //idx是元素下标, 如果想停止遍历,将stop改成YES。
if(idx == 1)
{
*stop = YES;
}
}];
NSString *str = [arr componentsJoinedByString:@","]; 用逗号将字符串数组进行连接。
NSArray *arr = [str componentsSeparatedByString:@","]; 字符串内部以逗号区分成一个数组
NSMutableArray: 是NSArray的子类,可以改变数组的长度。可以动态增加和删除元素。
NSMutableArray *arr1 = [NSMutableArray new];
NSMutableArray *arr1 = [[NSMutableArray alloc] init];
NSMutableArray *arr1 = [NSMutableArray array];
上面三种创建方式有意义,长度是0, 可以动态的添加和删除。
NSMutableArray *arr1 = [NSMutableArray arrayWithObjects:@"jack",@"Tom",nil]; --可以这样写
NSMutableArray *arr1 = @[@"jack",@"Tom"]; --- 报错--不能这样写。没有这种写法
[arr1 addObject:@"kaili"]; //新增元素
[arr1 addObject:arr]; //将数组arr作为一个整体对象添加到arr1中。长度只加了1.
[arr1 addObjectsFromArray:arr]; //将数组arr的所有元素加入到arr1中, 长度加了arr的长度。
[arr insertObject:@"lisi" atIndex:1]; 在某个下标前添加元素
[arr removeObjectAtIndex:1]; 删除指定下标的元素
[arr removeObject:@"lisi"]; 删除指定内容的元素, 如果有多个lisi,则都会删除。
[arr removeObject:@"lisi" inRange:NSMakeRange(0,3)]; //删除指定范围内的指定元素
[arr removeLastObject]; //删除最后一个元素
[arr removeAllObject]; //删除所有元素。
NSNumber:
NSArray不能存基本数据类型,但可以包装成NSNumber来进行存储
NSNumber *number1 = [NSNumber numberWithInt:10]; //还有numberWithFloat, 取 number1.floatValue;
NSNumber *number2 = [NSNumber numberWithInt:20];
NSNumber *number3 = @30; //简写形式
int num = 10;
NSNumber *number4 = @(num); //简写形式
NSArray *arr = @[number1, number2, number3, number4];
NSArray *arr = @[@10, @20]; //简写形式。
NSLog(@"%d", number1.intValue)
[arr writeToFile:@"/Users/AppleDisktop/abc.plist" atomically:NO]; 将数组保存到文件中。
NSArray *arr = [NSArray arrayWithContentsOfFile:@"/Users/AppleDisktop/abc.plist"]; 从文件中读出数组, arr != nil 表示读取成功
初始化结构体的方式
方式1:
NSRange range;
range.location = 3;
range.length = 4;
方式2:
NSRange range = {3, 4};
方式3:
NSRange range = {.location = 3, .length = 4};
方式4:
NSRange range = NSMakeRange(3, 4);
对象的打印:
NSLog(@"%@", NSStringFromRange(range));
NSDictionary与NSMutableDictionary键值对的存储方式:类似java的map:
NSDictionary *dic1 = [NSDictionary new];
NSDictionary *dic1 = [[NSDictionary alloc] init];
NSDictionary *dic1 = [NSDictionary dictionary];
上面三种创建方法字典数组中没有元素,所有NSDictionary没有意义,而NSMutableDictionary可以这样创建。
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:@"value1", @"key1", @"value2", @"key2", nil]; //注意先写值,再写键
NSDictionary *dict = @{@"key1":@"value1", @"key2":"value2"}; //上一句的简写形式
dict.count //个数
NSString *s1 = [dict objectForKey:@"key1"]; 取值
NSString *s1 = dict[@"key"]; 取值
遍历:
for(id key in dict) {
NSLog(@"%@", dict[key]); 这种方式遍历得到的是所有的键,再通过键拿到值。
}
通过block遍历键值
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonull obj, BOOL *_Nonnull stop) {
NSLog(@"%@ = %@", key, obj);
}]
NSMutableDictionary是NSDictionary的子类,可以增减元素:
NSMutableDictionary *dic1 = [NSMutableDictionary new];
NSMutableDictionary *dic1 = [[NSMutableDictionary alloc] init];
NSMutableDictionary *dic1 = [NSMutableDictionary dictionary];
上面三种方式有意义
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"value1", @"key1", @"value2", @"key2", nil]; //注意先写值,再写键
NSMutableDictionary *dict = @{@"key1":@"value1", @"key2":"value2"}; //报错, 不能这样写
[dict setObject:@"value1" forKey:@"key1"]; //新增键值对, 键已经有了,则会覆盖
[dict removeAllObject]; 删除所有键值对
[dict removeObjectForKey:@"key1"]; 删除指定键值对
BOOL b = [dict writeToFile:@"/Users/AppleDisktop/abc.plist" atomically:NO]; 将键值对保存到文件中。
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:@"/Users/AppleDisktop/abc.plist"]; 从文件中读出字典, dict != nil 表示读取成功
NSArray集合、NSDictionary字典集合都可以叫做集合
在MRC模式下,将对象存储到集合,计算+1, 当集合销毁是,里面的所有对象计数-1
使用@[]和@{}创建的集合,已经被autorelease过了。直接调用类方法创建时,也已经被autorelease过了。
在ARC模式下集合中的元素时强类型元素
NSFileManager: 操作磁盘文件
NSFileManager manager = [NSFleManager defaultManager];
NSString *path = @"/Users/Apple/Desktop/cc.mp3";
BOOL res = [manager fileExistsAtPath:path]; //判断这个文件是否存在
BOOL flag = NO;
BOOL res = [manager fileExistsAtPath:path isDirectory:&flag]; //先通过res表示路径是否存在, 再通过flag判断是否是文件夹,flag=YES是文件夹, 否则是文件
BOOL res = [manager isRreadableFileAtPath:path]; 是否有权限读这个文件, 但先要去判断这个文件是否存在
BOOL res = [manager isWritableFileAtPath:path]; 是否有权限写这个文件, 但先要去判断这个文件是否存在
BOOL res = [manager isDeletableFileAtPath:path]; 是否有权限删这个文件, 但先要去判断这个文件是否存在
NSDictionary *dict = [manager attributesOfItemAtPath:path error:nil]; 拿到文件的属性信息。
NSArray *arr = [Manager subpathsAtPath:path]; 拿到所有子目录包括孙子目录
NSArray *arr = [Manager contentsOfDirectoryAtPath:path error:nil]; 拿到所有子目录,不包括孙子目录
NSString *str = @"hh";
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding]; 字符串转二进制内容
BOOL res = [manager createFileAtPath:path contents:data attributes:nil]; //创建文件,第二个参数是文件的存储的二进制内容, 为空就传nil,第三个参数是文件的属性,nil表默认属性
BOOL res = [manager createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:nil]; //创建文件夹, 第二个参数YES表示可以多层创建,NO表示不能多层创建。
BOOL res = [manager copyItemAtPath:path1 toPath:path2 error:nil];文件拷贝
BOOL res = [manager moveItemAtPath:path1 toPath:path2 error:nil];文件移动
BOOL res = [manager removeItemAtPath:path1 error:nil];文件删除
CGPoint:坐标位置:是一个存储x、y的结构体:
NSPoint和CGPoint是同一个结构体,是一样的,只是取了一个别名。
CGPoint p1;
p1.x = 20;
p1.y = 30;
CGPoint p2 = {20, 30};
CGPoint p3 = {.x = 20, .y = 30};
CGPoint p4 = CGPointMake(20, 30);
CGPoint p5 = NSMakePoint(20, 30);
CGSize: 表示控件的宽度和高度。是一个结构体。 NSSize是CGSize的别名。
CGSize size;
size.width = 20;
size.height = 30;
CGSize size = {20, 30};
CGSize size = {.width = 20, .height = 30};
CGSize size = CGSizeMake(20, 30);
CGSize size = NSMakeSize(20, 30);
CGRect和NSRect: 是一个结构体:有一个CGPoint和一个CGSize变量构成。
CGPoint p = {20, 30};
CGSize size = {20, 30};
CGRect rect;
rect.origin = (CGPoint){20, 30};
rect.size = (CGSize){20, 30};
CGRect rect = {p, size};
CGRect rect = {.origin = p, .size = size};
CGRect rect = CGRectMake(20, 30, 40, 50);
CGRect rect = NSMakeRect20, 30, 40, 50);
NSValue:
因为CGPoint等上面讲的结构体不是OC类,所以不能存入NSArray中,必须包裹一层类才能存储,系统提供了NSValue来包裹这些结构体。
CGPoint p1 = CGPointMake(10, 20);
CGPoint p2 = CGPointMake(30, 40);
NSValue *v1 = [NSValue valueWithPoint:p1];
NSValue *v2 = [NSValue valueWithPoint:p2];
NSArray *arr = @[v1, v2];
for(NSValue *v in arr) {
NSLog(@"%@", NSStringFromPoint(v.pointValue));
}
字符串与日期转换:
NSDate:
NSDate *date = [NSDate new]; //获取当前的时间, 得到的是0时区的时间。打印部分+000表示是0时区
//时间转字符串
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.dateFormate = @"yyyy-MM-dd HH:mm:ss";
NSString *str = [formatter stringFormDate:date]; //转成指定时间格式字符串,会自动转成当前系统的时区。
//字符串转时间:
NSString *strDate = @"2022-12-22 12:11:33";
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.dateFormate = @"yyyy-MM-dd HH:mm:ss";
NSDate *date = [formatter dateFromString:strDate]; //得到的会自动转成0时区的时间。
//5000秒后的时间
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:5000]; //得到的也是0时区的时间。
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:8*60*60]; //北京时区的时间
//两个时间差
double sj = [endDate timeIntervalSinceDate:startDate]; //时间差 秒
//得到日期的各个部分:
方法一:formatter.dateFormate = @"yyyy"; 通过格式获得年月日
方法二:通过日历对象获得
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *date = [NSDate date];
NSDateComponnets *com = [calendar components:NSCalendarUnitYear|NSCalendarUnitMmoth|NSCalendarUnitDay fromDate:date]; //可以获得年月日
int year = com.year;
字符串copy: 无论在MRC还是ARC下,如果属性的类型是NSString类型的,@property参数使用copy.----以后NSString一直用copy
@property(nonatomic, copy) NSString *name;
copy是一个方法,定义在NSObject类之中, 作用:拷贝对象。
NSString *str1 = @"jack"
NSString *str2 = [str1 copy];
str1和str2的地址一样。没有产生新对象。
如果是NSMutableString使用copy拷贝,地址会不一样,产生了新对象。 并且copy后产生的是一个NSString而不是NSMutableString.
NSMutableString *str2 = [str1 mutableCopy];
使用mutableCopy拷贝后,不管是NSString还是NSMutableString, 地址都会不一样,产生一个新对象,这个新对象是NSMutableString.
NSMutableString的值赋值给Person的name对象, 如果NSMutableString的值改变了,name的值也会跟着改变,我们不希望这样,而是希望像NSString一样,不会跟着改变。
则:setter方法里面:_name=[name copy]; 即@property里面用copy修饰。
Person *p1 = [Person new];
Person *p2 = [p1 copy]; //报错,虽然copy方式是NSObject的方法,但是copy方法要调用copyWithZone方法,这个方法定义在NSCopying协议中,所有Person类必须继承NSCopying协议才能调用copy方法。
@interface Person: NSObject <NSCopying>
@end
@implementation Person
- (id)copyWithZone:(NSZone*)zone
{
//深拷贝
Person *p1 = [Person new];
p1.name = _name;
p1.age = _age;
return p1;
//或者浅拷贝
return self;
}
@end
单例模式:
创建对象总是会调用alloc方法,alloc方法总是调用allocWithZone方法。真正创建对象的是allocWithZone方法。
约定:单例方法必须以shared类名或者default类名作为返回单例的方法
@interface Person : NSObject
+ (instancetype)sharedPerson;
或者+ (instancetype)defaultPerson;
@end
@implementation Person
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
//return [super allocWithZone:zone];
static id instance = nil;
if(instance == nil) {
instance = [super allocWithZone:zone];
}
return instance;
}
+ (instancetype)sharedPerson
{
return [self new];
}
+ (instancetype)defaultPerson
{
return [self new];
}
@end
//使用:
Person *p1 = [Person sharedPerson];
object-c UI篇
添加文档帮助器
UI框架:
UIKit:创建和管理应用程序的用户界面
QuartzCore:提供动画特效以及通过硬件进行渲染的能力
CoreGraphics: 提供2D绘制的基于C的API
CoreLocation:使用GPS和WIFI获取位置信息
MapKit:为应用程序提供内嵌地图的接口
AVFoundation:音视频处理框架。
案例:计算器程序:
file---新建project---ios(osx是桌面端)---application----SingleView Application----(Bundle Identifer 唯一标识符,自动生成,通过组织标志+项目名,如果项目名第一位是数字变成-, 其他中文变成-)
点击运行,在模拟器为空白
Main.storyboard文件, 更改尺寸,找到控件箱,拖拽进入界面。
UILable ---文本标签
UIButton ---按钮
UITextField ----文本输入框
UIView ----所有控件的祖先
UIViewController ----点击等管理UIView的所有交互, 一个页面先创建UIViewController控制器,再创建UIView,在创建子View, 管理所有的view.
找到UIViewController的名字,来找到控制文件,如:ViewController.h, ViewController.m
选中计算按钮,点击右上角的辅助编辑框, 出来了类似ViewController.m的文件内容,
将树列表里面的计算按钮或者模拟器里面的计算按钮,拖到ViewController.m里面的@interface里面,下拉选择Action---自动生成方法的声明和实现。
取消脱线---右键控件树---取消Touch Up Inside-----再删除代码。
如果手动连线:先声明方法和实现方法,返回值必须是IBAction, 然后右键控件树,拖动右侧的点到声明的函数上。 或者不右键,而是直接Ctrl + 鼠标拖一下试试。
控件树选择输入框,拖到类扩展处,即@interface里面,Connection选择OutLet而不是Action ---命名:txtAA1, 如果是lable, 则命名:lblBB1
在Action函数中实现:
NSString num1 = self.txtNum1.text;
NSString num2 = self.textNum2.text;
int n1 = [num1 intValue];
int n2 = num2.intValue;
int result = n1 + n2;
self.lblResult.text = [NSString stringWithFormat:@"%d", result];
//方法一
//[self.txtNum2 resignFirstResponder]; ----因为num2最后调出的键盘,所有要num2来收回键盘
//[self.txtNum1 resignFirstResponder]; ----两个文本框都收回键盘
//方法二
[self.view endEditing:YES]; //页面的根View,让根View停止编辑,子控件的键盘就都回去了。
选择工程, 右侧出现:Deployment Target 下拉选择这个工程兼容的最低版本的ios
Main Interface 下拉选择storyboard加载运行哪个界面。
Main.storyboard有一个灰色的大箭头,表示启动时运行的第一个界面, 可以拖动箭头或者勾选Is Initial View Controller来改变。
启动了模拟器后台, 最上面菜单Hardware和debug可以控制模拟器,模拟物理按键等。
案例2:按钮按下状态:
将图片或者图片所在文件夹拖拽到目录树的Images.xcassets目录下
拖拽一个按钮, 背景图设置为文件夹里面的图片名字,设置宽高
选中按钮,点击StateConfig 选择高亮, 即点击时的状态。按下有灰色的状态,所以Type选择Custom就没有灰色的蒙板了。
Alt+鼠标拖动 -----复制相同的按钮
目录树中双击按钮可以给按钮改名字
CGRect originFrame = self.btnIcon.frame; //获取按钮的大小和位置, frame大小和位置,
CGPoint centerPoint = slef.btnIcon.center; //center只修改位置,表示控件中心点的坐标, centerPoint.x,
CGReact originBounds = self.btnIcon.bounds; //bounds只修改大小, 位置是0, 只有宽高有值,originBounds.size.width = 10; 中心点不变放到,originFrame放大是左上角放大。
//NSStringFromCGRect(originBounds); 字符串输出CGReact的值
originFrame.origin.y -= 10;
originFrame.size.width += 10;
也可以:originFrame.size = CGSizeMake(originFrame.size.width + 10, originFrame.size.height + 10); //如果重新设置宽高没有效果, 取消布局的:Use Auto Layout就可以了。
self.btnIcon.frame = originFrame; //修改按钮的大小和位置
注意:如果是一个结构体,必须将结构体赋值给一个变量,再修改里面的值,而不能直接一直点点点的去赋值。即错误:self.btnIcon.frame.orign.y -= 10 ---报错
//transform属性:平移缩放旋转函数:CGAffineTransformMakeTranslation()、CGAffineTransformMakeScale()、CGAffineTransformMakeRotation() ---表示创建transform属性
CGAffineTransformTranslation()、CGAffineTransformScale()、CGAffineTransformRotation() ----表示在某个transform的基础上进行 叠加
self.btnIcon.transform = CGAffineTransformMakeTranslation(0, 50); //多次调用也只会移动一次,因为是相对原始位置
或 self.btnIcon.transform = CGAffineTransformTranslate(self.btnIcon.transform, 0, 50) //每次调用都会移动一次,因为是相对当前位置, M_PI_4表示90度
清空之前设置的transform属性: view.transform = CGAffineTransformIdentity;
UIView的常见属性:
superview:获得自己的父控件对象
subviews:获得自己的所有子控件对象: self.myview.subviews
tag:控件的ID(标识),父控件可以通过tag来找到对应的子控件: UITextField *txt = (UITextField *)[self.view viewWithTag:20];
transform: 平移缩放旋转属性
- (void)addSubview:(UIView *)view; 添加子控件view
- (void)removeFromSuperview; --从父控件移除
- (UIView *)viewWithTag:(NSInteger)tag; 根据一个tag标识找出对应的空间,一般都是子控件。
动画方式一---比较少用:
[UIView beginAnimations:nil context:nil]; //开启动画
[UIView setAnimationDuration:2]; //设置动画持续时间
self.btnIcon.frame = originFrame; //将这句放到动画代码中间,放大缩小就会有动画效果了。
[UIView commitAnimations]; //提交动画
动画方式二---常用:
[UIView animateWithDuration:1.0 animations:^{
self.btnIcon.frame = originFrame;
}]
多个按钮指向一个方法,拖线时, Type选UIButton, Event选Touch Up Inside, Arguments选 Sender, 其他按钮连线时,拖线要指到同一个方法
目录树中选中按钮, Tag给一个整数值,随便填,用于区分多个按钮调用同一个方法,
- (IBAction)move:(UIButton *)sender {
//sender.tag 就是用来区分哪个按钮的, tag是上面填的数字。
}
动态创建控件:
ViewController.m中:
- (void)viewDidLoad { //表示界面View加载成功的回调函数
[super viewDidLoad];
UIButton *button = [[UIButton alloc] init]; //创建按钮
//UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; //第二种创建按钮,设置按钮的类型
[button setTitle:@"点我" forState:UIControlStateNormal]; //普通状态下的文字
[button setTitle:@"点我干啥" forState:UIControlStateHighlighted]; //设置高亮下的文字
UIImage *imgNormal = [UIImage imageNamed:@"btn_01"]; //获得一张图片
[button setBackgroudImage:imgNormal forState:UIControlStateNormal]; //普通状态下的背景图
UIImage *imgHight = [UIImage imageNamed:@"btn_02"]; //获得一张高亮图片
[button setBackgroudImage:imgHight forState:UIControlStateHighlighted]; //高亮状态下的背景图
[button setTitleColor:[UIColor redColor] forState:UIContrrolStateNormal];
[button setTitleColor:[UIColor blueColor] forState:UIControlStateHighlighted];
button.frame = CGRectMake(50,100,100,100); //必须设置大小和位置,否则无法显示
[button addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside] //单击事件
[self.view addSubview:button]; //将按钮加载到控制器的view中
}
- (void) buttonClick //单击事件,因为不是拖线,所以返回void就可以
{
}
安装帮助文档和ios7.1模拟器
苹果开发者网址:https://developer.apple.com/cn/
安装模拟器,拖进指定系统目录 --- 百度一下,退出重启
帮助文档:help--Document and Api Reference ----进去后又很多文档,有些点击不进去,上面没有安装。
帮助文档压缩包---拖到指定系统目录
需要装三个文档:OSX文档即mac电脑应用的开发文档,iOS手机应用的文档, DeveloperTools6_0_1是XCode的开发文档。
ApDelegate.h 代理文件
Images.xcassets 图片放的位置
Supporting Files 音频视频等其他文件放的位置
UIWindow的前缀UI表示UIKit框架的类
图片控件:
在Support Files里面添加一个数据文件:pic.plist
内容:Type选择Array, item类型选择:Dictonary, item里面的icon字段类型选String,title类型String
在ViewController.m里面加载pic.plist文件:
@property (nonatomic, strong)NSArray *pic;
@property (nonatomic, assign) int index; //表示第几张
- (NSArray *)pic
{
//属性的get方法
if(_pic == nil) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"pic.plist" ofType:nil]; //ofType不是空时, 则文件名不要后缀
NSArray *array = [NSArray arrayWithContentsOfFile:path];
_pic = array;
}
return _pic;
}
//下一张图片
- (IBAction) next {
self.index++;
NSDictionary *dict = self.pic[self.index];
self.lblIndex.text = [NSString stringWithFormat:@"%d/%ld", self.index, self.pic.count];
self.imgViewIcon.image = [UIImage imageNamed:dict[@"icon"]];
self.lblTitle.text = dict[@"title"];
}
帧动画:很多图片切换效果:
@property(nonatomic, copy) NSArray *animationImages; //存放图片的数组
@property(nonatomic) NSTimeInterval animationDuration; //动画持续时间
@property(nonatomic) NSInteger animationRepeatCount; //循环次数,默认无限循环
- (void)startAnimating; //开始执行帧动画
- (void)stopAnimating; //停止执行帧动画
- (BOOL) isAnimating; //动画是否正在执行
-----------------------------------------
NSMutableArray *arrayM = [NSMutableArray array];
for(int i = 0; i < 81; i++) {
NSString *imgName = [NSString stringWithFormat:@"drink_%02d.jpg", i];
//UIImage *imgCat = [UIImage imageNamed:imgName]; //imageNamed加载图片,一直会在内存中,直到程序结束才释放,所有加载很多图片时不能用这种方式加载图片。
NSString *path = [[NSBundle mainBundle] pathForResource:imgName ofType:nil]; //获取图片的完整路径, 这种方式图片要放到Suporting Files文件夹下,而不是Images.xcassets文件夹下。
UIImage *imgCat = [UIImage imageWithContentsOffile:path]; //这种方式加载的图片不会一直在内存中。
[arrayM addObject:*imgCat];
}
self.imgViewCat.animationImages = arrayM; //一直拿到图片数组,很占用内存,动画完后要设置null
self.imgViewCat.animationDuration = self.imgViewCat.animationImages.count * 0.2;
self.imgViewCat.animationRepeatCount = 1;
[self.imgViewCat startAnimating];
[self.imgViewCat performSelector:@selector(setAnimationImages:) withObject:nil afterDelay:self.imgViewCat.animationDuration]; //延时时间,动画执行完后,将animationImages=nil,来释放内存
UIButton和UIImageView的区别?
相同点:都能显示图片
UIButton可以监听事件,UIImageView默认不能监听事件
UIButton可以在不同状态下显示不同图片
UIButton既可以显示图片也可以显示文字,UIImageView只能显示图片
文档注释:
/**
在方法上加这个注释,调用函数时会提示这个注释, 或者///会自动生成/***/, 但需要安装xcode插件:VVDocumenter.Xcode, 重启
*/
加载图片是,写文件名有提示,需要安装xcode插件:KSImageNamed.xcode插件。重启
九宫格例子:
在viewDidLoad生命周期里面加动态创建控件的代码:
UIView *appView = [[UIView alloc] init];
appView.backgroundColor = [UIColor blueColor];
appView.frame = CGRectMake(0, 0, 40, 40);
[self.view addSubview.appView];
上面代码for循环运行很多遍就变成九宫格了。
//文字大小和对齐的设置
lblName.font = [UIFont systemFontOfSize:12];
lblName.textAlignment = NSTextAlignmentCenter;
//设置按钮的文字和背景图
[btnDownload setTitle:@'下载' forState:UIControlStateNormal];
[btnDownload setTitle:@'已安装' forState:UIControlStateDisabled];
[btnDownload setBackgroundImage:[UIImage imageNamed:@"buttongreen"] forState:UIControlStateNormal];
btnDownload.titleLable.font = [UIFont systemFontOfSize:14]; //设置按钮文字的大小
xib和storyboard都是自定义组件,然后自定义组件也可以拖拽到页面,达到系统控件一样的效果。
xib是轻量级, 即小控件,只描述界面上的一部分。
storyboard是重量级的,描述多个界面,以及界面之间的跳转关系
字典转模型:原因:字典获得值没有提示,模型有提示,模型可以看到编译错误,模型有继承等关系。
CZApp.h文件, 字典的字段映射到模型对象上
@interface CZApp : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *icon;
@end
@implementation CZApp
- (instancetype)initWithDict:(NSDictionary *)dict
{
if(self = [super init]) {
self.name = dict[@"name"];
self.icon = dict[@"icon"];
}
return self;
}
+ (instancetype)appWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
@end
字典转模型:
CZApp *model = [[CZApp alloc] init];
model.name = dict[@"name"]; //其中dict是plist的某一个字典
model.icon = dict[@"icon"];
instancetype:表示在哪个类里面就表示哪个类型。可以父子之间返回值分别是父和子。
xib: 一个xib里面可以有多个组件
新建文件---ios---user interface---empty---CZApp.xib, 开发时后缀是xib, 到了手机上,打包后,后缀变成了nib
拖拽一个View---设置大小(无法设置,可以改变显示方式,在改大小)---拖拽出一个复杂的view
在ViewController.m里面加载xib
NSBundle *rootBundle = [NSBundle mainBundle];
NSLog(@"%@", [*rootBundle bundlePath]); 打印aa.app的安装路径
UIView *appView = [[rootBundle loadNibNamed:@"CZAppView" owner:nil options:nil] lastObject]; //因为拿到的是数组,一个xib有多个组件,用lastObject获取最后一个组件。
给appView里面的子控件赋值,使用Tag来区分控件赋值的方式不推荐。
UIImageView *imgViewIcon = (UIImageView*)[appView viewWithTag: 1000]; //不推荐
推荐写法:新建一个同名的类:CZAppView.m和CZAppView.h, 继承UIView,
选择CZAppView.xib, 给里面最外层的View设置Class为CZAppView,
选择CZAppView.xib, 里面的控件拖线到CZAppView.h文件中,给三个属性命名:如imgViewIcon
CZAppView *appView = [[rootBundle loadNibNamed:@"CZAppView" owner:nil options:nil] lastObject];
appView.imgViewIcon.image = [UIImage imageNamed:appModel.icon];
上面两句可以将模型数据appModel直接传给CZAppView.m,然后在CZAppView里面设置图片和设置文字等等, 注意如果用这种模型的方式,上面拖线应该在CZAppView.m中拖线,这样属性就是私有的,不能在外面访问。
上面在控制器中写的生成appView的几句代码,也可以通过静态方法封装到CZAppView.m中。
上面项目文件太乱,右键项目,New Group新建几个组,即新建几个文件夹:Models、Views、Controllers、Others
Others: AppDelegete.h、AppDelegete.m
Controllers: ViewController.h、ViewController.m
Views:CZAppView.xib、CZAppView.h、CZAppView.m, 主界面的xib放入Views
Models:CZApp.m、CZApp.h
上面建的是虚拟文件夹,在实际文件夹中没有,所以引入路径也不需要改。
xib里面的逻辑,如果让他的父控件加上某个view呢?[self.superview addSubview:lblMsg]; ---在xib外面弹一个半透明的消息框。
案例2:图片猜主题
重写ViewController.m里面重写方法,设置状态栏深色还是浅色,隐藏状态栏
//改变状态栏的文字颜色为白色
- (UIStatusBarStyle) preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
//隐藏状态栏
- (BOOL)prefersStatusBarHidden
{
return YES;
}
如果一个按钮不想让用户点击,可以取消勾选的:User Interaction Enabled
insert选项是内边距,可以输入4个方向的内边距
如果要取消按钮点击的灰色状态,可以取消勾选:Highlighted Adjuest Image
选中一段代码,长按,鼠标拖动到右侧,输入<#name#>, 这样输入一段代码的前几个字,回车,就会自动补全,并且name会高亮选中,删除重新输入名字。
btnCover.frame = self.view.bounds; //将按钮的宽高设置为屏幕的宽高。
[self.view bringSubviewToFront:self.btnIcon]; //将某个本来被覆盖的btnIcon按钮图片,设置在屏幕的最上方,即:把图片设置到阴影的上面。
控件放大缩小或者重新设置坐标和宽高,没有效果,需要取消自动布局才能生效:Use Auto Layout
动态设置按钮的单击事件:
[btnCover addTarget:self action:@selector(smallImage) forControlEvents:UIControlEventTouchUpInside]; 其中smallImage是一个返回void的方法。
动态去除阴影:
[self.cover removeFromSuperview];
动态删除所有子控件
while (self.answerView.subviews.firstObject) {
[self.answerView.subviews.firstObject removeFromSuperview];
}
或者:
[self.answerView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; //makeObjectsPerformSelector表示数组中所有元素都调用removeFromSuperview这个函数。
动态添加点击事件,并且传点击的按钮为参数:
[btnOpt addTarget:self action:@selector(optionButtonClick:) forControlEvents:UIControlEventTouchUpInside];
- (void)optionButtonClick:(UIButton *)sender
{
sender.hidden = YES; //按钮隐藏
//拿到lable的文字是:lblaa.text
//下面button获得文字则不一样,因为有状态
NSString *text = [sender titleForState:UIControlStateNormal];
或者 NSString *text = sender.currentTitle; //获取当前状态下的文字。
}
self.optionsView.userInteractionEnabled = NO; //表示它自己,和它的所有子控件都不能去交互,即不能点击。
[sender setTitle:nil forState:UIControlStateNormal]; //nil表示将按钮的文字设置null
btnOpt.tag = i; 创建一个btn时,同时设置一个tag值。
answerBtn.tag = sender.tag; //一个界面可以有多个tag相同的控件。
NSMutableString *userInput = [NSMutableString string]; //新建一个可变的字符串
[userInput appendString:btnAnswer.currentTitle]; //给字符串后面添加字符
对话框:
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle@"提示操作" message:@"恭喜通关" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", @"哈哈", nil];
[alertView show];
监听对话框哪个按钮被点击,需要设置代理;delegate表示代理, 所以需要继承代理:@interface ViewController () <UIAlertViewDelegate>
重写UIAlertViewDelegate代理方法:
- (void)alertView:(UIAlertview *) alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSLog(@"%ld", buttonIndex); //按钮的索引
}
aa.png、aa@2x.png、aa@3x.png为什么要多张?
1.屏幕大小不一样,图片要做不同的版本。
2.启动应用图片,在不同的地方,要求的图片大小不一样。
系统会自动找匹配大小的图片。
设置应用图标,图片名字是:AppIcon.png, 这张图片也有很多大小版本,如Appicon29x29.png, Appicon29x29@2x.png, Appicon57x57.png,Appicon57x57@3x.png等很多图片。
启动图片设置:
选择项目---App Icons and Launch Images --- Launch Images Source ---点击---确定---删除Launch Screen File里面的内容--打开Images.xcasset---打开刚刚新建的文件夹---将图片拖进去---很多不同机器版本的图片
UIScrollView滚动和缩放控件:
拖一个UIScrollView到屏幕
拖一个UIImageView到UIScrollView中
图片设置到UIImageView中,设置UIImageView的宽高,要超出UIScrollView, 如1600x1200,
拖线这个两个控件,设置属性名
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView.contentSize = self.imgView.frame.size; //告诉UIScrollView里面内容的实际大小,这样才可以滚动。
或者:self.scrollView.contentSize = self.imgView.image.size;
}
右侧的设置框中:取消勾选:Scrolling Enabled表示不能滚动:scrollEnable = NO。
没有接收触摸事件:userInteractionEnabled = NO;
有的版本机器需要取消:Use Auto Layout才会滚动。
UIScrollView的frame.size是本事大小, contentSize是里面内容实际大小。
contentOffset表示里面内容偏移量。可以获取和设置这个属性
CGPoint point = self.scrollView.contentOffSet;
point.x += 150;
point.y += 150;
self.scrollView.contentOffset = point;
如果要平滑移动,不仅可以用以前的那种动画,还可以用下面这一句:
[self.scrollView setContentOffset:point animated:YES];
隐藏滚动指示器
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = NO;
contentInset属性:内边距,拖动的内容与UIScrollView的边距:
self.scrollView.contentInset = UIEdgeInsetsMake(10,20,30,40);
BOOL bounces属性: 是否需要弹簧效果
如果横向上不要滚动,直接给contentSize横向上设置0;
contentSize的高度可以是最后一个空间的y坐标+它的高度。
拖动一个控件,不要拖到模拟器上,而是拖动树形列表中的某个下面,这样可以防止ScrollView占了全屏,拖一个控件不在ScrollView包裹里面的情况。
代理监听滚动事件:
滚动事件不能用addTarget来添加事件,而必须用代理来添加滚动事件。
代理对象是通过控件的delegate属性来关联起来的。
一般控件的代理协议的命名规则都是:控件名+Delegate
@interface ViewController() <UIScrollViewDelegate>
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView.contentSize = self.imgView.image.size;
self.scrollView.delegate = self;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
NSLog(@"即将开始拖拽");
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(@"正在滚动");
NSString *pointStr = NSStringFromCGPoint(scrollView.contentOffset);
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
NSLog(@"拖拽完毕,不是滚动完毕,因为惯性,滚动函数还会调用很多次");
}
除了上面手动写代理外,还可以拖线,选择控件树的控件,右键--delegate---拖线到控件树的控制器---拖线的方式注释掉代码self.scrollView.delegate = self,代理函数依旧可以运行。
UIScrollView实现缩放,如图片缩放:
必须设置最大放大倍数,和最小缩小倍数。
只能对单个子控件进行缩放。
@interface ViewController () <UIScrollViewDelegate>
拖线设置代理
重写代理方法:
- (UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imgView; //返回的,表示对哪个子控件进行缩放。
}
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView.maximumZoomScale = 3.5; //设置最大的放大比例
self.scrollView.minnumZoomScale = 0.5; //设置最大的缩小比例
}
scrollViewWillBeginZooming, scrollViewDidZoom,scrollViewDidEndZooming:分别表示开始缩放、正在缩放、缩放完毕的代理方法。
模拟器:鼠标+alt键实现双指缩放
@optional关键字,表示遵守这个代理协议的类,只需要实现某几个方法,而不需要实现所有方法。代理对象的源码一般有这个关键字。
@required关键字,需要实现代理协议的所有方法,否则报警告。
轮播图:
UIScrollView动态添加5个UIImageView,y坐标不变,x坐标一直往右叠加。
self.scrollView.pagingEnabled = YES; //则有分页效果,会自动配置一页一页的翻页,一个UIImageView占一页,不会一屏中显示两个半页。即UIScrollView的宽度作为一页。
self.scrollView.showsHorizontalScrollIndicator = NO; //隐藏水平指示器。
UIPageControl是分页指示器,需要加上这个控件来分页指示器。
//[self.scrollView bringSubviewToFront:self.pageControl] //将scrollView中的pageControl控件放大控件的最上面,这样其他控件就不会挡住他了。
拖UIPageControl控件在UIScrollView的平级的树形结构上,再改坐标让他显示。
UIScrollView和UIPageControl关联上:
self.pageControl.numberOfPages = 5; //设置总页数
self.pageControl.currentPage = 0; //设置当前页
UIScrollView代理方法:
- (void) scrollViewDidScroll:(UIScrollView)scrollView
{
CGFloat offsetX = scrollView.contentOffset.x; //这宽度可以再加半页的宽度,这样滚动超过半页,分页指示器就会加1了。
int page = offsetX/scrollView.frame.size.width; //偏移的坐标除以每一页的宽度
self.pageControl.currentPage = page;
}
两种不同的定时器:
1. NSTimer---时间间隔比较大,1秒或几秒
2. CADisplayLink ---时间间隔比较小, 0.0几秒
NSTimer的创建方式:
1. 调用timerWithXxx创建的timer,把这个timer对象手动加到"消息循环"中才能启动。
2. 调用scheduleTimerWithXxx创建的timer,自动启动,创建完成后自动启动。
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(scrollImage) userInfo:nil repeats:YES];
参数一是时间间隔,参数二target是目标对象,参数三是调用目标对象的方法,第四个参数nil, 第五个参数是否循环定时。
给UIScrollView设置偏移,就会滚动到指定位置了。通过UIPageControl获得当前页码。NSInteger page = self.pageControl.currentPage;
如果是最后一页,就滚动到第一页。
通过动画的方式滚动:
[self.scrollView setContentOffset:CGPointMake(offsetX, 0) animated:YES];
拖拽开始时停止计时器,拖拽结束后启动计时器。
开始拖拽:
[self.timer invalidate]; //停止计时器,调用invalidate一旦停止计时器,那么这个计时器就不可以在重用了。下次必须重新创建一个新的计时器对象。
self.timer =nil;
结束拖拽:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(scrollImage) userInfo:nil repeats:YES];
UITextView不同于UILable, UITextView继承于UIScrollView, 所以文字较多是也可以滚动。
UI控件的响应优先级,如拖拽等,比网络请求和定时器的优先级高。
修改self.timer的优先级与控件一样,需要先创建一个消息循环对象:
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:self.timer forMode:NSRunLoopCommonModes]; //两种优先级模式:NSDefaultRunLoopMode, NSRunLoopCommonModes, 他们都是字符串,在定义文件的开头找到。
应用启动图标:
Images.xcasets里面AppIcon里面有很多空的图片占位,里面应该放各种大小的图片。
LaunchScreen.xlib是启动界面,会默认这个界面的截图作为启动界面。
选中项目---Launch Screen File选项默认就是LaunchScreen.xlib, 可以改变即点击Launch Images Source ---launchImage文件夹--删除lauchScreen---在里面放置各种图片就会自动找图片了。---点击默认没有图片的占位--属性---可以看到需要图片的大小。
NSString *sandBox = NSHomeDirectory(); //app的安装目录,可以在这个目录找到很多文件
View大小不对?
NSLog(@"%@", NSStringFromCGRect(self.view.frame)); //如果launch的启动图片只有iphone5,而没有iphone6的,即使用的模拟器是iphone6的,但是实际view的宽度还是iphone5的。所以启动图应该给全。
屏幕的宽度是根启动图的大小来定的,所以启动图要给全。----launchImage文件夹启动方案有这个问题。
UITableView: ------类似android的listview
继承UIScrollView
两种样式:
UITableViewStylePlain----不同的item用不同的样式也是用这个
UITableViewStyleGrouped --- 每组分隔开来
拖拽UITableView到界面
dataSource指定数据源对象---必须遵守UITableViewDataSource协议,必须实现协议的某些方法。设置数据源的两种方式:代码方式和拖线方式;
代码方式:
self.tableView.dataSource = self;
拖线方式:
右键UITableView---dataSouce拖线到ViewController的树上。
@interface ViewController() <UITableViewDataSource>
实现必须的方法:一般会实现三个方法
- (UIInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
//返回分组数, 默认分一组,不实现这个方法就是分一组
return 1;
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInsection:(NSInteger)section
{
//每组有几条数据,section是分组的下标,如果是1组,下标为0
/* if(section == 0) {
return 200;
} else if (section == 1) {
return 300;
} */
return 100;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *) indexPath
{
//告诉UITableView每一组的每一行显示什么单元格内容
//indnexPath有section第几组,row第几行
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableVIewCellStyleDefault reuseIdentifier:nil]; //UITableViewCellStyleXXXXX 有很多默认颜色的cell
//默认的cell有三个控件,分别为textLable、imageView、detailTextLable;
cell.textLabel.text = @"hello";
return cell;
}
选中UITableView---右边属性---style--Plain(默认不分组)--Group(分组要选择这个)--界面才会分组显示
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return @"组头显示的标题"
}
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
{
return @"组尾的描述"
}
model的两个实现方法的新的写法:
@implementation CZGroup
- (instancetype)initWithDict:(NSDictionary *)dict
{
if (self = [super init]) {
//以前写法是对每个属性赋值:self.title = dict[@"title"];
//现在如果属性名和字典名一样,只需要写下面一句,每个属性都会自动赋值
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
+ (instancetype)groupWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
//在控制器ViewContraller.m里面懒加载plist数据
= (NSArra *)groups
{
if(_groups == nil) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"cars_simple.plist" ofType:nil];
NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *arrayModel = [NSMutableArray array];
for (NSDictionary *dict in arrayDict) {
CZGroup *model = [CZGroup groupWithDict:dict];
[arrayModel addObject:model];
}
_groups = arrayModel;
}
return _groups;
}
- (BOOL)prefersStatusBarHidden
{
return YES; //隐藏状态栏
}
分支Type选择Plain,默认不分组时,组头标题和组尾描述也会显示出来,并且滑动时最上方的组标题一直会置顶
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle seuseIdentifier:nil]; //这个cell带主标题和子标题的
还有:UITableViewCellStyleValue1、UITableViewCellStyleValue2
cell.accessoryType = UITableViewCellAccessoryNone; //单元格右侧不显示任何图标
cell.accessoryType = UITableViewCellAccessoryCheckmark; //单元格右侧显示√图标
cell.accessoryType = UITableViewCellAccessoryDetailButton; //单元格右侧显示信息图标
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton; //单元格右侧显示信息和右箭头两个图标
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; //单元格右侧显示右箭头图标
cell.acessoryVIew = 某个UIView, 则单元格右侧可以放任何控件图标
self.tableView.rowHeight = 120; //设置每一行的行高
对于每一行的行高不一样,需要用代理方法实现:
@interface ViewController() <UITableViewDataSource, UITableViewDelegate>
- (CGFloat)tableView:(UITableView* )tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
int rowNum = indexPath.row;
if(rowNum % 2 == 0) {
return 60;
} else {
return 120;
}
}
cell.backgroundColor = [UIColor blueColor]; //设置单元格的背景颜色, 没有选中的背景颜色,可以用背景View
UIView *bgView = [[UIView alloc] init];
bgView.backgroundColor = [UIColor greenColor];
cell.backgroundView = bgView;//设置单元格的背景View,不仅可以设置颜色,也可以设置图片
cell.selectedBackgroundView = bgView;//设置单元格的选中背景View
self.tableView.separatorColor = //分割线颜色
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;//表示没有分割线 //分割线样式
self.tableView.tableHeaderView = //tab头, 一般放广告
self.tableView.tableFooterView = //tab尾,一般放加载更多
单元格复用,不复用很影响效率:默认出了屏幕的单元格会销毁,入屏幕会重新生成单元格,这样的单元格没有复用。
单元格复用的前提是两个单元格显示的是一样的。
为每组样式的单元格设置 重用ID
从缓存池中根据 重用ID 拿取是否有缓存,有缓存就取出,没有就新建。
static NSString *myId = @"aaa_cell"; //不同类型的cell设置不同的id
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: myId]; //用参数tableView,根据id查找cell
if (cel == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:myId]; //创建cell时,设置Id
}
字典模型嵌套字典模型,最里面可以用以前的方式,但是外层的模型必须修改:
- (instancetype)initWithDict:(NSDictionary *)dict
{
if(self = [super init]) {
// self.title = dict[@"title"];
// self.cars = dict[@"cars"]; //这一句是错误的,这个是字典赋值,不是模型赋值,必须用循环语句解开
//上面两句等于下面一句
[self setValuesForKeysWithDictionary:dict];
//重新对cars进行字典转模型
NSMutableArray *arrayModels = [NSMutableArray array];
for (NSDictionary *item_dict in dict[@"cars"]) {
CZCar *model = [CZCar carWithDict:item_dict]; //调用内层的字典转模型
[arrayModels addObject:model];
}
self.cars = arrayModels;
}
return self;
}
模型嵌套模型时,plist的懒加载是一样的。
模拟器右侧属性style可以选择是否分组,不管是否分组,都可以设置分组头,且都能显示,只是不分组拖动是组头会黏贴在顶部。
设置列表的右侧索引栏,如ABCDEFG....
用数据源协议方法
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
//return @[@"aaa",@"bbb"]; //跟文字没有关系,只跟顺序索引有关
return [self.groups valueForKeyPath:@"title"]; //去除数组里面的每个对象title, 组成一个title数组。
}
获取一个对象的属性可以用下面两种方式:
NSString *pname = p.name;
或者 NSString *pname = [p valueforKeyPath:@"name"]; //这种常用于字典转模型的方式。
NSArray *array = @[p1, p2, p3]; //其中p1等就是一个Person对象,
NSArray *arr = [array valueForKeyPath:@"name"]; //获取每个对象的name,组成一个新的String数组。
点击UITableView的某一行,修改这一行的数据,刷新这一行:
点击行,需要用代理:
didSelectRowAtIndexPath选中行,didDeselectRowAtIndexPath取消选中行
- (void)tableView:didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//indexPath.row
CZHero *hero = self.heros[alertView.tag];
hero.name = name;
[self.tableView reloadData]; //重新刷新列表,刷新整个tabview
//NSIndexPath *idxPath = [NSIndexPath indexPathForRow:alertView.tag insection:0]; //指定组和每组的第几行
//[self.tableView reloadRowsAtIndexPaths:@[idxPath] withRowAnimation:UITableRowAnimationAutomatic]; //指定刷新某几行
}
任何控件都有一个tag,如果传整数时,可以设置tag来实现传整数。
自定义cell
两种方式:1.通过xib实现自定义cell, 2.完全手写代码实现自定义cell
新建模型类文件:新建--ios--Source--Cocoa Touch Class---文件名
新建xib文件:新建--ios--User interface --- Empty---文件名CZGoodsCell.xib
拖一个UITableViewCell到xib中,设置宽高,然后拖各种控件进入cell中。
新建一个类继承UITableViewCell, 类名:CZGoodsCell,
将上面的xib的class指定为CZGoodsCell,
编译一下,然后对xib的控件进行拖线,自动在CZGoodsCell中出现这些控件的属性,拖线到@interface GZGoodsCell() 代码区中。
在UITableView中使用xib创建单元格:
CZGoodsCell *cell = [[[NSBundle mainBundle] loadNibNamed:@"CZGoodsCell" owner:nil options:nil] firstObject];
//用这种xib的方式,如果设置重用ID呢,来提高单元格复用呢?
选中xib,右边属性有一个identifier:输入goods_cell这个重用id;
static NSString *ID = @"good_cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if(cell == nil) {
cell = [[[NSBundle mainBundle] loadNibNamed:@"CZGoodsCell" owner:nil options:nil] firstObject];
}
//将上面这一段代码,可以封装到CZGoodsCell的类方法中,这一只需一句代码搞定。
赋值不能用 cell.xxx = model.title; 因为会暴露很多xib的属性;
给CZGoodsCell加goods属性,
cell.goods = model;
在CZGoodsCell中设置goods的set方法:
- (void)setGoods:(CZGoods *)goods
{
_goods = goods;
self.lblTitle.text = goods.title;
}
在ViewController.m中
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.rowHeight = 44; //不加这一句,xib的方式cell可能会有警告,有警告就加这一句
}
拖拽UITableViewCell时,这个控件的第一层是一个Content View, 再里面才是我们拖拽的各个小控件。
btn.frame = CGRectMake(20, 50, 30, 100);
self.tableView.tableFooterView = btn; //footerView里面控件,宽铺满,高默认值,只能设置x和高,宽和y没有效果,所以一般tableFooterView是自定义View,也用xib。
footer的xib中,拖拽View, 属性Size选Freeform否则大小无法设置,
self.waitingView.hidden = YES; 或NO, 没有show属性。
加载中旋转按钮控件,默认是不转的,必须勾选Animating,才会不停的旋转。
自己写代理:ViewController.m有goods数据,CZFooterView.m没有goods数据,却要用,用代理来用:
在CZFooterView.h这个头文件中写代理协议:
@class CZFooterView;
@protocol CZFooterViewDelegate <NSObject>
@required
- (void)footerViewUpdateData:(CZFooterView *)footerView; //这种代理函数必须至少有一个参数,而且第一个参数是控件自己,将自己传给contrller去使用。
@end
@interface CZFooterView : UIView
@property (nomatic, weak) id<CZFooterViewDelegate> delegate;
@end
CZFooterView.m在这个实现类中,如点击函数中:
if ([self.delegate respondsToSelector:@selector(footerViewUpdateData:)]) { //判断是否实现了这个代理方法
[self.delegate footerViewUpdateData:self];
}
ViewController.m在控制器中实现这个代理:
footView.delegate = self;
@interface ViewController () <UITableViewDataSource, CZFooterViewDelegate>
- (void) footerViewUpdateData:(CZFooterView *)footerView
{
//在这里拿到了footerView,有用goods数据,就可以做很多操作了。
}
延迟一段时间再执行代码dispatch_after---GCT的那个:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (init64_t)(1.0 * NSEC_PER_SEC)), disatch_get_main_queue(), ^{
//1秒后执行的代码写在这里
});
将某一行滚动到最上面、中间、最下面
[self.tableView scrollToRowAtIndexPath:idxPath atScrollPosition:UITableViewScrollPositionTop animated:YES];// 将Top换成Bottom\Mid等
新建CZHeaderView.xib, 拖一个UIScrollView, 再拖一个UILableView, 这个xib作为UITableView的HeaderView:
怎么知道xib的自定义控件加载完成到页面了?覆写方法awakeFromNib
- (void) awakeFromNib
{
//比如自定义控件中有scrollView, 要设置它的contentSize就是在这个函数里面, 会自动回调这个方法
self.scrollView.contentSize = ........;
}
加载xib文件的两种方式:
方式一是前面学的: [[[NSBundle mainBundle] loadNibNamed:@"CZGoodsCell" owner:nil options:nil] firstObject];
方式二: UINib *nib = [UINib nibWithNibName:@"CZGoodsCell" bundle:nil];
UIView *vw = [[nib instantiateWithOwer:nil options:nil] lastObject];
代码写自定义View:
如果每个cell的高度不一样,有的有图片,有的没有图片,文字高度也不一样,有的有会员,有的没有会员:
这种情况下就不能用xib的方式自定义view了, 需要用代码生成自定义View:
字典模型类继承自NSOject:
Main.storyboard这个里面有UIViewController控制器,控制这个整个界面控件的显示, 可以删除里面的控制器,重新拉一个UIViewController控制器。
UITableViewController是专为UITableView定制的控制器,里面自动加了一个UITableView控件,并且自定义设置里面的代理等
我们自己拉的UITableViewController左边没有箭头,说明没有依附到界面,勾选 Is Initial View Controller, 表示是启动控制器。
这个控制器默认指向系统的UITableViewController, 这样我们无法操作,改为指向我们自己的ViewController, 并且让它继承UITableViewController,
或者重新建一个CZTableViewController, 继承UITableViewController, 将控制器右边属性class改成:CZTableViewController, 继承的类里面默认已经有各种协议了。
其他的控制器默认有self.view表示当前控制器最顶一层的UIView, 而UITablerViewController顶层View是self.tableView, 这时self.view和self.tableView是一样的。
不用xib来自定义View, 也需要新建类文件:CZWeiboCell继承UITableViewCell:
重写UITableViewCell的initWithStyle方法:
- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) //调用父类方法
{
//自己创建子控件
UIImageView *imgViewIcon = [[UIImageView alloc] init]; //这个控件后面要用,声明到@interface中去:@property (nonatomic, weak) UIImageView *imgViewIcon;
[self.contentView addSubView:imgViewIcon];
self.imgViewIcon = imgViewIcon;
UILable *lblNickName = [[UILable alloc] init];
[self.contentView addSubView;lblNickName];
}
return self;
}
在CZWeiboCell.h中添加模型属性:
@property (nonatomic, strong) CZWeibo *weibo;
在CZWeiboCell.m中重新set方法:
- (void)setWeibo:(CZWeibo *)weibo
{
_weibo = weibo;
//设置内容
self.imgViewIcon.image = [UIImage imageNamed:model.icon]; //如果这个图片一直显示相同的图片,可以在创建这个view的同时设置image,这样就不需要重复设置了。
self.libNicName.text = model.name;
//设置图标
self.imgViewIcon.frame = CGRectMake(10, 10, 35, 35);
//CGReactGetMaxX(self.imgViewIcon.frame) //指的是控件右边界x轴坐标。
//计算文字的大小:第一个参数是最大宽高,设置不限定, options参数是指定计算方式,attributes:表示字体大小,
NSDictionary *attr = @{NSFontAttributeName: [UIFont systemFontOfSize:12]};
CGSize nameSize = [@"要计算的字符串" boundingRectWithSize:CGSizeMake(MAXFLOAT,MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:attr context:nil].size;
CGFloat nameW = nameSize.width;
CGFloat nameH = nameSize.height;
//计算之后, 发现文字显示不正常,因为要在创建UILable的同时设置字体大小:lblNickName.font = [UIFont systemFontOfSize:12];
//上面就计算好了每个空间的坐标和宽高,下面要计算这个cell的高度了:= 最下方控件的最大y值 + margin
rowHeight = CGRectGetMaxY(self.imgViewPicture.frame) + margin;
}
然后通过代理方法,设置行高,但是rowHeight无法用代理将数值给控制器, 怎么办呢? 原因:因为控制器是先要设置行高,再通过模型数据设置各个控件的宽高,代理的执行顺序是反的, 计算行高太晚了。
解决办法:将计算行高的代码提前计算, 提前到懒加载数据后就计算,让模型数据就存储了各个控件的坐标和宽高frame,和这个cell的高度;
新建一个CZWeiboFrame的模型文件,里面包含CZWeibo的模型文件,和各个控件的frame, 和cell的frame
@class CZWeibo
@interface CZWeiboFrame: NSObject
@property (nonatomic, strong) CZWeibo *weibo; //数据模型, 这种这个数据的时候,重写setWeibo方法,并且在里面设置下面的各个控件的frame
@property (nonatomic, assign, readOnly) CGRect iconFrame; //保存头像frame
@property (nonatomic, assign, readOnly) CGRect nameFrame; //昵称的frame
@property (nonatomic, assign, readOnly) int rowHeight; //cell的行高
@end
新的懒加载方法如下:
- (NSArray *)weiboFrames
{
if (_weiboFrames == nil) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"weibos.plist" ofType:nil];
NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *arrayModels = [NSMutableArray array];
for (NSDictionary *dict in arrayDict) {
CZWeibo *model = [CZWeibo weiboWithDict:dict];
CZWeiboFrame *modelFrame = [[CZWeiboFrame alloc] init];
modelFrame.weibo = model;
//下面就要设置frame的各种计算参数了,计算方法上面一句写过,搬过来就可以了,设置各种frame数据,最好放在CZWeiboFrame的setWeibo方法里面进行封装
----略
[arrayModels addObject:modelFrame];
}
_weiboFrames = arrayModels;
}
return _weiboFrames;
}
然后将CZWeiboCell.m的代码改成:
self.imgViewIcon.frame = self.weiboFrame.iconFrame;
self.lblNickName.frame = self.weiboFrame.nameFrame;
最好在控制器的设置行高的方法中返回 self.weiboFrames[indexPath.row].rowHeight;
重复使用同一个计算值,可以设置为宏:
#define nameFont [UIFont systemFontOfSize:12]
设置UILable可以自动换行,在创建UILable后就可以设置:
lblText.numberOfLines = 0;
并且要设置宽度才会自动换行:
在上面计算字体大小时设置最大宽度:CGSizeMake(300,MAXFLOAT), 这样就好自动换行了。
为什么控件的引用和控件的代理要用weak?
UI控件的代理属性必须使用weak来修饰,其他情况则视情况而定。
原因:因为控件强引用代理,代理又强引用控件,导致循环引用,则内存就释放不了。
微信聊天界面项目:
如果列表不占用整个屏幕的高度,就不能用UITableViewController, 只能用UIViewController;
CGRect在#import <CoreGraphics/CoreGraphics.h>包下
UIView不能设置背景图片, 需要在UIView增加UIImageView在设置背景图片, UIImageView中不能拖其他按钮,但是可以用代码给UIImageView中添加其他的按钮。
获取屏幕的宽度:CGFloat screenW = [UIScreen mainScreen].bounds.size.width; //UIScreen在#import <UIKit/UIKit.h>
设置文字 大小: lblTime.fongt = [UIFont systemFontOfSize:12];
设置文字 居中: lblTime.textAlignment = NSTextAlignmentCenter;
设置文字 颜色: [btnText setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
设置文字 自动换行:btnText.titleLable.numberOfLines = 0;
取消UITableView的分割线:在控制器viewDidLoad中:self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
设置UITableView的背景颜色:self.tableView.backgroundColor = [UIColor colorWithRed:236/255.0 green:111/255.0 blue:99/255.0 alpha:1.0]
设置UITableCell的背景色透明:CZMessageCell.m中创建完后:self.backgroundColor = [UIColor clearColor];
设置UITableView的单元格不让选中,即也不会有选中高亮了,在控制器viewDidLoad中:self.tableView.allowSelection = NO;
设置图片背景,背景拉伸变形怎么办,类似android有.9.png图片? ios的方式是指定图片的中的某一点,除了这一点的其他部分原大小保存,这个点平铺其他大小。
UIImage *imageNormal = [UIImage imageNamed: @"chat_send_nor"];
imageNormal = [imageNormal stretchableImageWithLeftCapWidth:imageNormal.size.width*0.5, topCapHeight:imageNormal.size.height*0.5]; //指定图片中心点进行平铺放大。
iso常用的按钮控件里面其实还有一个lable控件所以可以改按钮设置背景,在给他里面的lable设置背景
btnText.backgroundColor = [UIColor purpleColor];
btnText.titleLable.backgroundColor = [UIColor greenColor];
btnText.contentEdgeInsets = UIEdgeInsetsMake(15, 20, 15, 20); //给btn和lable中间增加内边距
//要让input的文本框左边光标和边框有点距离,可以在左边加一个UIView
UIView *leftView = [[UIView alloc] init];
leftView.frame = CGRectMake(0, 0, 5, 1); //宽5,高1的view
self.txtInput.leftView = leftView;
self.txtInput.leftViewMode = UITextFieldViewModeAlways; //左侧view永远显示。
通知可以监听电量的改变,键盘的弹出等
通知步骤:
通知发布
通知监听
通知移除
创建发布者:
MySender *sender1 = [[MySender alloc] init];
创建监听者:
MyListener *listener1 = [[MyListener alloc] init];
//创建系统通知对象:
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
监听通知必须放在发送通知的前面
[notificationCenter addObserver:listener1 selector:@selector(m1) name:@"tzname1" object:sender1]; //参数1:监听者, 参数2:监听者接受通知的处理函数,自己随便定义,参数3:通知名字,如果是nil,则监听sender1的所有通知, 参数4:发送通知者,如果是nil,则监听所有发送者的tzname1的通知。 如果参数3和参数4都是nil则监听所有发送者的所有通知。
发送通知
[notificationCenter postNotificationName:@"tzname1" object:sender1 userInfo:@{@"aaa": @"bbb", @"ccc": @"ddd"}];
//移除监听通知, 在MyListener类中:对象销毁是移除
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
MyListener的监听方法:
- (void)m1: (NSNotification *)noteInfo
{
//参数noteInfo封装了通知的所有内容
NSLog(@"%@", noteInfo); //noteInfo--name(通知名),object(发送者),userInfo(信息json)
}
键盘的弹出通知:键盘弹出和收取时,整个view上移和下移键盘的距离
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
- (void) keyboardWillChangeFrame:(NSNotification *)noteInfo {
CGRect rectEnd = [noteInfo.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat keyboardY = rectENd.origin.y;
CGFloat tranformValue = keyboardY - self.view.frame.size.height;
self.view.transform = CGAffineTransformMakeTranslation(0, tranformValue);
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
}
点击UITableView时键盘收起的代理方法:
- (void)scrollViewWillBeginDragging:(UIScrollView *) scrollView
{
[self.view endEditing:YES];
}
选中input按钮,右侧属性,return key改成Send, 则键盘会有发送按钮。
监听键盘的Send键的点击事件:
UITextField控件拖线代理, 实现UITextFieldDelegate代理的方法:
- (BOOL) textFieldShouldReturn: (UITextField *)textField
{
return YES;
}
设置界面案例:
UITableView静态单元格, 表示里面的数据固定,不会运行时改变, 必须用UITableViewController来做。
UITableView右边属性:Content改成Static Calles表示静态单元格
选中UITableView右边属性:Style改成Grouped, 再Sections设置分几组 这种设置顺序没有组标题,反过来设置就会有组标题
QQ好友列表案例: 二级列表: 分小学、中学、大学; 每个里面有很多同学:
分三组, 每组有很多个学生
先显示第二级的cell,跟以前的分组实现一样
字典嵌套转嵌套模型:
在控制器的懒加载代码中:
- (NSArray *)groups
{
if (_groups == nil) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"friends.plist" ofType:nil];
NSArray *arrayDicts = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *arrayModels = [NSMutableArray array];
for (NSDictionary *dict in arrayDicts) {
CZGroup *model = [CZGroup groupWithDict:dict];
[arrayModels addObjet:model];
}
_groups = arrayModels;
}
return _groups;
}
因为是嵌套字典,所以对外层的字典模型需要修改:即CZGroup.m文件中修改:
- (instancetype)initWithDict:(NSDictionary *)dict
{
if (self = [super init]) {
[self setValuesForKeysWithDIctionary:dict];
//下面是嵌套字典才要加的
NSMutableArray *arrayModels = [NSMutableArray array];
for (NSDictionary *dict_sub in self.friends) {
CZFriend *model = [CZFriend friendWithDIct:dict_sub];
[arrayModels addObject:model];
}
self.friends = arrayModels;
}
return self;
}
自定义单元格:@interface CZFriendCell : UITableViewCell
组标题只有文字用:- (NSString *) tableView:(UITableView *)tableView titleForHeaderInsection:(NSInteger)section
组标题改变样式,并且有各种子控件时用下面:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
//不要直接创建UIView对象返回,因为这样实现不能重用UIView, 而应该用继承UITableViewHeaderFooterView的view
//1.获取模型数据
CZGroup *group = self.groups[section];
//2.创建UITableViewHeaderFooterView
static NSString *ID = @"group_header_view";
UITableViewHeaderFooterView *headerVw = [tableView dequeueReusableHeaderFooterViewWithIdentifier:ID];
if(headerVw == nil) {
headerView = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:ID];
}
//3.设置数据
headerVw.group = group
//4.返回view
return headerVw;
}
//上面这些是用系统的UITableViewHeaderFooterView, 但是我们要自定义一个这样的View,如下:CZGroupHeaderView:
+ (instancetype)groupHeaderViewWithTableView:(UITableView *)tableView
{
static NSString *ID = @"group_header_view";
UITableViewHeaderFooterView *headerVw = [tableView dequeueReusableHeaderFooterViewWithIdentifier:ID];
if(headerVw == nil) {
headerView = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:ID];
}
}
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier
{
if(self = [super initWithReuseIdentifier:reuseIdentifier]) {
//创建自定义View的子控件
UIButton *btnGroupTjitle = [[UIButton alloc] init];
[self.contentView addSubview:btnGroupTitle];
UILable *lblCount = [[UILable alloc] init];
[self.contentView addSubview:lblCount];
}
return self;
}
//重新group的设置方法
- (void)setGroup:(CZGroup *)group
{
_group = group;
//设置数据
[self.btnGroupTitle setTitle:group.name forStateUIControlStateNormal];
slef.lblCount.text = @"aaa"
//设置frame
self.btnGroupTitle.frame = self.bounds; //不能这样写,因为这是还没有加到控制器中,所以bounds都是0,要用下面的方法:
}
设置frame也可以在回调函数中调用:
- (void)layoutSubviews
{
[super layoutSubviews];
self.btnGroupTitle.frame = self.bounds;
}
在控制器中统一设置组标题的高度
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.sectionHeaderHeight = 44;
}
//按钮内容左对齐
btn.contentHorizontalAlignment= UIControlContentHorizontalAlignmentLeft;
//按钮内容左边距
btn.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 0);
//设置按钮文字和图片的距离
btn.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, 0);
如何谁先列表的二级展开和收起的功能呢?
只要控制组列表中的某个组,行数量的回调函数控制为0, 就会收起来了。
为模型数据的组数据加一个收起和展开的状态
状态改变后, 通过代理让控制器去刷新列表,在代理方法中写:
[self.tableView reloadData]
或 只刷新某个组:
NSIndexSet *idxSet = [NSIndexSet indexSetWithIndex:groupHeaderView.tag];tag是设置代理设给控件加上tag,来确定点击的是哪个组
[self.tableView reloadSections:idxSet withRowAnimation:UITableViewRowAnimationFade];
图片旋转, 每个控件加入到父控件都会回调这个方法:
- (void)didMoveToSuperView
{
self.btnGroupTitle.imageView.transform = CGAffineTransformMakeRotation(M_PI_2); //恢复原来角度,用0,
}
图片变形是因为模式不对:
//设置图片居中模式
btnGroupTitle.imageView.contentMode = UIViewContentModeCenter;
//图片超出部分不要截取掉
btnGroupTitle.imageView.clipsToBounds = NO;
因为控件重用,导致很多状态被另外一行复用了,所以要重置状态。
为什么这次不用xib做呢,因为无法拖UITableViewHeaderFooterView这个控件,导致要手写自定义控件。
app管理列表案例, 使用模板实现单元格:
在Main.storyboard模板中,拖Cell单元格控件到里面,
给单元格设置Identifier为:app_cell
在控制器中:
static NSString *ID = @"app_cell";
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:ID];
这样就可以复用上面我们拖的单元格了。
拖的单元格高度是修改右边属性的Row Height: 60
然后在viewDidLoad中设置:self.tableView.rowHeight=60; 这样看到的效果和运行的效果才是一样的
KVC:
[p1 setValue:@"李四" forKeyPath:@"name"]; 给person的name属性赋值 李四, KVC赋值方式更灵活,因为键可以用字符串变量来代替传入。
[p1 setValue:@10 forKeyPath:@"age"]; 给person的age属性赋值10
Dot d = [[Dot alloc] init];
d.name = @"哈士奇"
[p1 setValue:d forKeyPath:@"dog"];
[p1 setValue:@"哈士奇2" forKeyPath:@"dog.name"];
获取属性值:
NSString *name = [p1 valueForKeyPath:@"name"];
NSString *dogName = [p1 valueForKeyPath:@"dog.name"];
字典给对象赋值:
NSDictionary *bz = @{
@"name": @"aaa",
@"age": @20,
@"dog": @{@"name": @"bbb"}
};
[p1 setValuesForKeysWithDictionary:bz];
NSDictionary *dogDict = (NSDictionary *)p1.dog; //嵌套字典,第二层获得的还是字典。
//将对象转字典
NSDictionary *dict = [p1 dictionaryWithValuesForKeys:@[@"name", @"age", @"dog"]]; //要指定将哪些字段转字典, 但是多层的dog属性还是一个对象,不是字典。
屏幕适配:
farme方式:设置frame的具体值的方式,不会适配 ---不推荐---测试时简单实用
autoresizing方式:设置相对于父控件的位置 ---不推荐
AutoLayout自动匹配:即可以参照父控件,也可以参照兄弟控件 ---使用这种方式,自动会禁用autoresizing, 是互斥的。
size classes + autolayout方式: 不同的大小屏幕,显示在不同的位置,不同大小屏用不同规则:如4.0屏显示左上角,5.0屏显示右下角。
预览:点击右上角的双圆----出现编辑器----在点击编辑器的双圆---Preview----出现预览界面----删除----左下角的加号图标----可以添加各种屏幕进行预览 --- 双击缩小---一屏放多个
autoresizing: 取消掉AutoLayout, 右侧属性尺子就会出现autoresize了。
外面4个箭头跟4边的距离
内部2个箭头表示里面的控件的宽高随父控件的宽高变化而变化。
代码实现autoresizing:
外层蓝色view:
UIView *blueView = [[UIView alloc] init];
blueView.backgroundColor = [UIColor blueColor];
blueView.frame = CGRectMake(0, 0, 200, 200);
[self.view addSubview:blueView];
内层红色view
UIView *redView = [[UIView alloc] init];
redView.backgoundColor = [UIColor redColor];
[blueView addSubview:redView];
redView.frame = CGRectMake(0, blueView.frame.size.height-50, blueView.frame.size.width, 50)
//设置autoresizing, 先点击右边属性禁用autolayout
redView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin |UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
UIViewAutoresizingFlexibleLeftMargin //左边可变,右边固定
UIViewAutoresizingFlexibleRightMargin = 1 //右边可变,左边固定
UIViewAutoresizingFlexibleWidth = 1 //宽度随父控件的变化而变化
然后通过按钮点击改变外层View的宽高,看看效果
AutoLayout:
模拟器界面,正下方的wAny和hAny是设置size classes的, 右下角的4个小图标是设置autolayout布局的,
4个小图标分别是:
对齐: Leading Edges----左对齐
Trailing Edges ---右对齐
Top Edges ---上对齐
Bottom Edges ---下对齐
Horizontall Centers ----水平居中对齐
Vertical Centers ---垂直对齐
Baseline ----基线对齐
Horizontal Center in Container ---在父容器水平对齐
Vertical Center in Container ---在父容器垂直对齐
pin固定:设置距离父控件的距离,设置宽高,设置和其他控件一样宽高,设置比例一致,
Resolve Auto layout issues: 解决自动布局的一些问题,--一些辅助作用, 常用于删除当前控件的约束,或者删除所有控件的约束。
第四个图标:很少用:一般会自动选择后代元素随父元素的变化而变化
设置完后,左上角有红色圆圈表示错误,点击它会有提示:缺乏约束或者多个约束冲突
黄色圆圈是警告:表示控件的frame不匹配导致的,点击黄色圈,点击fix解决就行。
控制器就用默认的600x600, 来做适配。-----autolayout推荐用这个
加了约束后,点击 Add 2 Constraints后, 约束才会生效。
上边距设置0时,不是屏幕的最顶端,而是紧挨着状态栏,可以下拉箭头选择紧挨着屏幕,就可以了。
左右边距设置0时,左右有空白,可以设置左右边距为-16, 或者取消下面的勾选:Constrain to margins
多次更新约束是报错,因为约束冲突, 解决办法:Update Frames: Items of New Contraints ----表示使用新的约束替换旧的约束。
设个两个控件高度一样的约束: 同时选中两个view, 选择Equal Heights, 只有选择两个view, 才能选到这个。
选择一个view, 约束右边距是相对于另外一个view的一个值, 选择两个view, 设置宽度一样。
右边约束,必须鼠标拖动某个view在另外一个view的右边才可以选,否则无法找到右边的view.
对齐约束时,需要注意选择view的顺序, 需要设置的view要先选择。
右边属性锥子图标:first Item: 第一个选中的view
Second Item: 第二个选中的view
Relation: Equal ---两个Item的关系--相等
Constant: 第一个item在第二个item的基础上加多少
Mutipiller: 第二个item是第二个item的多少倍。-----第一个item的宽度是第二个item的宽度的一半,就将这个改成0.5就行。
即: first = (second + constant) * mutipiller;
水平居中---然后修改居中方式,比如右边边界和父控件水平居中----就是宽度是父控件的一半。
约束规则:
1. 当给view添加约束不依赖其他view时,比如宽高约束,则约束添加在自己上面。
2. 当view的约束是相对于父控件时, 约束会添加在父控件上, 如水平居中和垂直居中对齐。
3. 两兄弟之间有约束时,约束就会添加在他们的父控件上,比如两兄弟控件等宽和等高约束。
4. 如果两个控件有约束, 但是他俩没有相同的父控件,而有相同的爷爷控件,则约束会加在爷爷控件上。
通过代码添加约束,就必须熟悉约束规则,才知道在哪个控件加约束代码。
代码添加约束:
控制器中:
- (void) viewDidLoad {
[super viewDidLoad];
//创建蓝色View
UIView *blueView = [[UIView alloc] init];
blueView.backgroundColor = [UIColor blueColor];
//测试时,可以先添加frame,然后添加约束时,在删掉,这一步:略
[self.view addSubview:blueView];
//创建红色view
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
//禁用autoreszing
blueView.translatesAutoresizingMaskIntoConstraints = NO;
redView.translatesAutoresizingMaskIntoConstraints = NO;
//创建并添加约束
//创建蓝色view约束
//参数解释: A对象 的 某属性 等于 B对象 的 某属性 乘于 倍数 加 偏移量 ====》 即: first = (second + constant) * mutipiller;
//设置高度约束,高度是:50
//不需要相对其他对象:所以B对象是nil, 所以B对象属性是not属性即:NSLayoutAttributeNotAnAttribute
NSLayoutConstraint *blueHC = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeHeight
relateBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:50];
//高度约束加给自己
[blueView addConstraint:blueHC];
//左边距30的约束, 将blueView.superview改成self.topLayoutGuide则表示距离屏幕顶部而不是状态栏下部
NSLayoutConstraint *blueLeft = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeLeft
relateBy:NSLayoutRelationEqual toItem:blueView.superview attribute:NSLayoutAttributeLeft multiplier:1.0 constant:30];
//边距约束加给父控件
[blueview.superview addConstraint:blueLeft];
//同样的方式给蓝色view加上 上边距30和有边距30, 注意右边距的constant:-30, 是负数
//创建红色view的约束:
//高度与蓝色view一样
NSLayoutConstraint redHC = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeHeight
relateBy:NSLayoutRelationEqual toItem:blueView attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0];
//边距约束加给父控件
[redView.superview addConstraint:redHC];
//同样的方式, 给红色view的top距离蓝色view 30
//同样的方式, 给红色view右边与蓝色view的右边对齐
//同样的方式, 给红色view宽度等于蓝色view宽度的0.5倍
}
通过约束执行动画:
找到对应的约束,拖线, 就跟view拖线到@interface中一样。: NSLayoutConstraint *viewTopConstraint;
点击一个按钮,执行下面代码:
self.viewTopConstraint.constant += 100;
加动画:
[UIView animateWithDuration:1.5 animations:^{
[self.myView layoutIfNeeded]; //上面重新设置了约束后,对约束的控件重新布局一下,就有动画了。
}]
选择一个控件,按住ctrl鼠标点击,向另外一个控件方向拖动,就会出现这个控件相对于另外控件的约束
视频2:
UIPickerView 显示一组或多组数据方便用户选择
也需要UIPickerViewDataSource数据源协议和UIPickerViewDelegate代理协议
模拟器中点击右键,有属性和代理等,拖线到模拟器上面控制器图标上, 这是另外一种和在控件树上拖线方式一样的方式。
因为有三列,所以分三组:
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return self.foods.count; //分组
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return [self.foods[component] count]; //每组多少行
}
#pragma mark - 代理方法, 显示数据
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
return self.foods[component][row];
}
//滚动选择某一行回调
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
NSString *selFood = self.foods[component][row];
}
//默认选择数据
- (void)viewDidLoad {
[super viewDidload];
for (int i = 0; i < self.foods.cound; ++i) {
[self pickerView:self.pickerView didSelectRow:0 inComponent:i];
}
}
随机数函数: arc4random()%(n+1); //0-n之间的随机数 包括n
arc4random_uniform(n+1); //0-n之间的随机数 包括n
UIPickerView选中数据:
[self.pickerView selectRow:ranNum inComponent:i animated:YES];
获取某列选中的行号
NSInteger selRowNum = [self.pickerView selectedRowInComponent:i];
字典, 数组里面的数组每一项都是字符串,则里面的数组不需要再解模型。
如果两列有联动关系,比如省市关系,则计算行数时:第一列的行数,直接返回父数组的大小, 第二例则要先获取第0列选中的行号, 通过行号获得子数组的大小,返回这个大小。
代理监听滚动完成 didSelectRow, 如果滚动的是第0列,则刷新第1列的数据: [pickerView reloadComponent:1];
数组越界bug, 显示每一行字符串时:titleForRow参数的函数,是滚动的时机就会调用,而不是滚动完成时调用,第1列显示市时,省其实还没有最终确定,所以两列同时滚动会数据越界。
返回行数的函数:numberOfRowsInComponent参数的函数,当component == 1时,这里去保存省的下标, 市数据的显示都从这个下标来拿取。
只显示一列,但是不仅要返回String, 还要有图片:实现pickerView返回UIView的代理方法:
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {
创建view, xib自定义view, 继承UIView,
给view设置数据
返回view
}
//设置UIPickerView每行的高度
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component {
return 83;
}
上面创建自定义View时也加上自定义View每行的高:
rowView.frame = CGRectMake(0, 0, self.view.bounds.size.width, 83);
UIPickerView三种方式:多列字符串且列之间不联动, 多列字符串列之间联动,一列自定义view
UIDatePicker 显示一个日期组件方便用户选择
案例:点击输入框,弹出日期选择:
UITooBar:就是一个view, 既可以放普通的一些view, 还可以放一些特殊view:比如:UIBarButtonItem
UIToolBar右边属性style有很多类型可以选中,如弹簧布局inputAccessoryView等。
UI控件用week, UI控件懒加载用strong, 懒加载表示事先加载,而且不需要每次进入都重新加载,比如点击输入框显示的日期选择,日期选择懒加载,只会加载一次,多次显示还是显示上次的。
@property (nonatomic, strong) UIDatePicker *datePicker;
@property (nonatomic, strong)UIToolbar *toolbar;
#pragma mark - 懒加载控件
- (UIDatePicker *)datePicker {
if(!_datePicker) {
_dataPicker = [[UIDatePicker alloc] init]; //自动占满键盘的位置
_datePicker.datePickerMode = UIDatePickerModeDate; //日期模式
_datePicker.local = [[NSLocale alloc] initWithLocaleIdentifier:@"zh-Hans"]; //本地化
}
return _datePicker;
}
- (void) viewDidLoad {
[super viewDidLoad];
self.textField.inputView = self.datePicker; //设置点击输入框弹出的键盘换成日期选中框。
self.textField.inputAccessoryView = self.toolbar; //键盘有一行操作行,datePicker也应该有,就是toolbar, 也是input点击出来的。
}
同理设置UIToolBar的懒加载:只需要设置高度,不需要设置宽度, 日期选择框上面没有取消和确定按钮,所以加上UIToolBar
_toolbar.bounds = CGRectMake(0, 0, 0, 44);
取消按钮:UIBarButtonItem *cancelItem = [[UIBarButtonItem alloc] initWithTitle:@"取消" style:UIBarButtonItemStylePlain target:self action:@selector(cancelItemClick)];
同理确定按钮: *doneItem
取消和确定按钮之间的弹簧:
UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
_toolbar.items = @[cancelItem, flexSpace, doneItem]; //将三个item添加到toolbar中。
点击确定:
NSDate *date = self.datePicker.date; //获取日期
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd";
NSString *dateStr = [formatter stringFromDate:date];
self.textFied.text = dateStr;
[self.view endEditing:YES]; //关闭键盘即关闭日期框。
项目中有一个Info.plist文件:
点加号, 加一个键:Bundle display name: 显示名, 则app图标显示的名字就是这个。
Bundle versions string short: 1.0 应用版本
Bundle version: 1, 公司内部的版本号
Bundle identifier: 应用的唯一标识, com.cyh.aaa
Main storyboard file base name: 引用启动的第一个布局文件
Support interface ---: 按钮的位置和方向
Info.plist的内容也可以在项目的编辑页面,修改各种属性
用代码获取Info.plist的内容:
NSDictionary *dict = [NSBundle mainBundle].infoDictionary;
pch文件:PrefixHeader.pch, 在xcode6 后就没有这个文件了。
新建文件---other---pch文件---就可以生成pch文件了。
用于包含一些常用的头文件和常用宏和使用c语言文件时需要一些配置
#import "HMTool.h"
选择项目配置, 搜索 PrefixHeader, 在debug或者release版本的pch文件的路径
UIApplication: 主要是执行应用级别的操作
获取: UIApplication *app = [UIApplication sharedApplication]; 多次获取,地址一样, 单例
app.applicationIconBadgeNumber = 1; 设置应用头上的数字,默认为0,事先要权限通知
app.networkActivityInidatorVisible = YES; 显示联网状态
Info.plist: 新增 View controller based status bar appearce : NO, 设置状态栏不受控制器管理。
app.statusBarHidden = YES; 设置状态栏隐藏
//app.keyWindow //主控制器
//app.windows //应用程序可见和不可见的window
AppDelegate.h 应用程序代理
main.m是应用程序的入口----里面是一个死循环。
加载自定义控制器:
删除配置文件中默认的main.storyboard, 即在Info.plist的main删除
在AppDelegate.m中:
- (BOOL)application:(UIApplication *) application didFinaishLaunchingWithOptions:(NSDictionary *)launchOptions {
//应用程序启动完成后调用的代理方法
//创建窗口设置大小
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//设置窗口的根控制器
HMViewController *hmVc = [[HMViewController alloc] init]; //HMViewController是自定义的一个继承UIViewController的控制器
hmVc.view.backgroundColor = [UIColor redColor]; //加了这一句,控制器就会懒加载
slef.window.rootViewController = hmVc;
//设置主窗口,并可见
[self.window makeKeyAndVisible];
}
//这样删除了箭头的控制器,也可以运行起来应用,代码动态的设置了自定义控制器。自定义控制加一个背景色,运行起来就可以看到效果。
手写UIWindow, 对第三方框架,特别提示类框架,需要手写UIWindow
UIWindow *redW = [[UIWindow alloc] initWithFrame:CGRectMake(20, 20, 200, 200)];
redW.backgroundColor = [UIColor redColor];
redW.hidden = NO;
[slef.view addSubView:redW]; //在现有的view中添加window。
3种创建控制器的方式:
1.纯代码方式 ---上面讲过
2.storyboard创建控制器
3.xib创建控制器
storyboard方式:
创建storyboard文件
加载storyboard文件中的控制器:---拖拽的方式
选中控制器,设置Storyboard Id属性:blue
在AppDelegate.m中
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *) launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIStoryboard *hmboard = [UIStoryboard storyboardWithName:@"HMStoryboard" bundle.nil];
//UIViewController *hmVc = [hmboard instantiateInitialViewController]; //这种方式加载的是带箭头的控制器
UIViewController *hmVc = [hmboard instantiateViewControllerWithIdentifier:@"blue"]; //加载指定id的控制器
self.window.rootViewController = hmVc;
[slef.window makeKeyAndVisible];
}
xib方式:
创建自定义控制类
指定xib文件
修改xib的fileOwner
将fileOwner的view拖线至xib内的view
将storyboar的创建控制器的代码改成:
HMXibViewController *xibVc = [[HMXibViewController alloc] initWithNibName:@"HMMD" bundle:nil];
如果HMXibView.xib和HMXibViewController.m 前面的字符HMXibView相同这个规律,可以用下面方式:
HMXibViewController *xibVc = [[HMXibViewController alloc] init];
如果名称与开工之前完全一致:
HMXibViewController *xibVc = [[HMXibViewController alloc] init]; 跟上面一样代码
如果创建控制器文件时,勾选Auto create XIB file 自会自动指定class,和自动拖好线fileOwner
导航控制器:UINavigationController:----系统的设置界面就是导航控制器做的
创建导航控制器
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *) launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
HMRedViewController *redVc = [[HMRedViewController alloc] init]; //导航控制器的根控制器, 在这个控制加一个按钮,点击进入绿色的子控制器
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:redVc];
//nav.viewControllers 导航里面的栈里面的所有控制器
self.window.rootViewController = nav;
[slef.window makeKeyAndVisible];
}
点击按钮进入绿色控制器:
HMGreenViewController *greenVc = [[HMGreenViewController alloc] init];
[self.navigationController pushViewController:greenVc animated:YES];
退出到上一个界面:
[self.navigationController popViewControllerAnimated:YES];
[self.navigationController popToRootViewControllerAnimated]; //返回导航根控制器
[self.navigationController popToViewController:self.navigationController.viewControllers[1] animated:YES]还可以返回到中间某个控制器
上面是通过纯代码实现导航控制器
下面通过stroyboard实现导航控制器:
在Mian.stroyboard中拖控件:NavigationController控制器, 在里面拖出多个控制器,右键拖线出跟控制器, 添加按钮--右键拖线--show---拖到指定的控制器, 不需要任何代码,就实现了导航的push功能。
返回上一个控制器、返回根控制器、返回指定控制器就无法通过拖线来实现,只能通过代码实现:
新建所有控制器的实现代码:即继承UIViewController就行,添加按钮:[self.navigationController popViewControllerAnimated:YES]; 也能访问到控制器对象。
Navigation的导航栏, 在左边的控件树中国,有NavigationItems, 选中,可以拖动很多控件到里面, 只有根导航控制器可以拖进去, 否则要先拖一个NavigationItems控制器,然后再拖其他控制器。
self.navigationItem //获取导航栏
self.navigationItem.title = @"标题"
self.navigationItem.titleView = adBtn; 标题控件
UIBarButonItem *left = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCamera target:self action:@selector(carmraClick)];
self.navitationItem.leftBarButtonItem = left; 导航栏左侧的按钮, rightBarButtonItem表示右侧按钮, rightBarButtonItems和leftBarButtonItems表示多个按钮,是一个数组
//返回按钮, 应该加在它的导航父页里面。
UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:nil action:nil];
self.navigationItem.backBarButtonItem = backItem;
//当这个控制器不是导航控制器的根控制器,在左侧放了按钮,那么系统的返回按钮将不存在。
控制器的View的声明周期:
viewDidLoad---界面加载完成
viewWillAppear---界面即将显示
viewDidAppear---界面已显示
viewWillDisappear--界面即将消失
viewDidDisapear---界面已经消失
导航控制器传值:storyboard传值:
选中两个界面之间的连线,右边属性框,有很多属性。这条连线名字叫Segue
拖线的方式实现跳转,通过监听按钮的点击事件,实现点击方法是无法做到传参数的, 而是应该重写回调方法实现, 如下:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
//segue: 包括idnetifier--标记, sourceViewController--源控制器, destinationViewController ---目标控制器
HMGreenViewController *greenVc = segue.destinationViewController;
greenVc.navigationItem.title = @"aaaa"
}
如果界面不怎么变化用storyboard做,否则用纯代码实现界面。
通讯录案例:
UITextField的输入框,监听输入内容,实现代理:UITextFieldDelegaate
代理方法:
textFieldShouldBeginEditing: 点击输入框时调用,返回YES表示可以编辑, 返回NO表示禁止编辑
textFieldDidBeginEditing: 获取焦点开始编辑时调用
textFieldShouldEndEditing: 是否允许结束编辑,如果返回NO, 则无法点击另外一个编辑框进行编辑,只能编辑当前编辑框。
textFieldDidEndEditing: 文本框失去焦点结束编辑
shouldChangeCharactersInRange: 返回YES,只会打印输入最后一个字母前的内容, 返回NO,则表示不能再输入字母。
上面代码无法监听文本框改变,找它的父类的监听方法,所以不需要代理
[self.usernameField addTarget:self action:@selector(textChange) forControlEvents:UIControlEventEditingChanged];
- (void)textChante {
//self.userameField.txt;
}
导航控制器拖线到另外一个控制器,可用show也可以用push,推荐show, push过期了,区别:push会自动加上UINavctonrollItem的抬头, show需要自己拖控件加。
自动型Segue: ctrl + 鼠标---按钮---拖线---到另外一个控制器: 适用于点击按钮无条件都跳转的情况
手动型Segue: ctrl + 鼠标---点击控制器上方最左侧的控制器图标---拖线---到另外要给控制器: 适用于点击按钮,在某种情况下才跳转,否则不跳转的情况。---拖线完成后必须给Identifier赋值
登录按钮监听的函数:
[self performSegueWithIdentifier:@"login2contact" sender:nil]; 在符合登录条件时,加这一句,就会跳转到指定的界面,segue是一条线,这条线指定到控制器。
从一个导航控制器跳转到另外一个导航控制器,都会回调下面这个方法,所以传参可以在这个方法中实现:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
ContactViewController * contact = segue.destinationViewController;
contact.aa = @"bb"; 来给另外一个控制器复制
}
值逆传,第二个界面B,传值给A:即第二个Nav界面传值给第一个Nav界面:
在B的控制器中:AddViewController.h中
#import <UIKit/UIKit.h>
@class AddViewController;
@protocol AddViewControllerDelegate <NSObject>
@optional
- (void)addViewController:(AddViewController*)addviewController withName:(NSString *)name addNumber:(NSString *)number;
@end
@interface AddViewController: UIViewController
@property (nonatomic, weak) id<AddViewControllerDelegate> delegate;
@end
在AddViewController.m中,点击添加按钮函数中:
if([self.delegate respondsToSelector:@selector(addViewController:withName:addNumber:)]) {
[self.delegate addViewController:self withName:self.nameField.text addNumber:self.numberField.text];
}
[self.navigationController popViewControllerAnimated:YES];
在A控制器中:ContactViewController.m中:
- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender //无论手动还是自动型,都会走线的这个方法
{
//AddViewController * add = segue.destinationViewController;
//add.delegate = self;
UIViewController * vc = segue.destinationViewController; //将B控制器,设置给父类
if([vc isKindOfClass:[AddViewController class]]) { //用于判断B控制器的类型,不同的子控制器,走不同的分支
AddViewController * add = (AddViewController *)vc;
add.delegate = self;
} else {
//如果是点击列表,进入下一个Nav控制器可以用下面方式, 可以对列表tableView直接拖线。
EditViewController * edit = (EditViewController *)vc;
NSIndexPath* path = [self.tableView indexPathForSelecteRow];
Contact* con = self.contacts[path.row]; //Contact是模型数据
edit.contact= con;
}
}
//重新代理方法, 继承声明协议
- (void)addViewController:(AddViewController*)addViewController withName:(NSString*)name andNumber:(NSString *)number //也可以将name和number封装成模型,直接传模型就可以了。
{
NSLog(@"%@----%@", name, number); //收到B控制器传过来的数据。
}
在nav控制器C中,编辑列表中的某一项, 保存编辑, 将模型数据:self.contact.name = self.nameField.text; 因为是传地址,所以这个界面数据改了,在控制器A界面的数据也会相应的改,
所以只需要在A带代理方法中,重新刷新tab列表; 也可以不用代理,只需要在控制器周期回调函数中刷新tab列表; 也可以用代理方法传列表的下标,通过下标修改控制数据,刷新列表。
[self.numberField becomeFirstRresponder]; //一进入界面,就让某个文本框弹出键盘
IOS应用数据存储的常用方式:
XML属性列表(plist)归档
Preference(偏好设置)
NSKeyedArchiver归档(NSCoding) --保存模型
SQLite3
Core Data
沙盒:表示文件夹
沙盒根目录:NSString *home = NSHomeDirectiory();
沙盒的目录一直在变的,所有需要打印出来在文件系统里面找。
查看沙盒的软件:SimPholder2, 点击后菜单上会出现一个沙盒图标。
根目录下有下面这些文件夹:
Documents: 保存需要持久化的数据,ITunes同步设备时会备份该目录
temp: 保存临时数据,ITunes不备份,系统可能会清除这个目录
Library/Caches: 保存需要持久化的数据,iTunes不备份, 一般存储体积大、不需要备份的重要数据
Library/Preference: 保存应用的所有偏好设置,ITunes同步设备时会备份该目录。
pList的存储:
存在沙盒的document的目录下:
NSString * homePath = NSHomeDirectory();
NSString * docPath = [homePath stringByAppendingString:@"/Documents"]; //字符串拼接方式一, 需要斜杠
NSString * docPath = [homePath stringByAppendingPathComponent:@"Documents"]; //字符串拼接方式二,不需要斜杠
NSString * docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; //方式三,搜索反射获得路径---推荐使用,参数1:documents路径标记,NSUserDomainMask:沙盒根目录标记,参数3:YES--推荐,NO会将home目录变成~波浪线。
NSString * filePath = [docPath stringByAppendingPathComponent:@"xx.plist"];
NSArray * array = @[@"1", @"2"];
[array writeToFile:filePath atomically:YES]; ----YES表示先写在缓存里面,在写到文件,可以防止断电等出现文件问题。
//取plist的内容:
NSArray * array = [NSArray arrayWithContentsOfFile:filePath];
Preference偏好设置: 不需要关系文件夹和文件的名字
NSUserDefaults * ud = [NSUserDefaults standardUserDefaults]; ---单例,一般standard开头的是单例
[ud setObject:@"value" forKey:@"key"];
[ud setBool:YES forKey:@"isOn"];
不是立即写入,写入马上获取,会失败,如果立即写入,加入这一句:
[ud synchronize];
获取:
[ud objectForKey:@"key"];
[ud boolForKey:@"isOn"];
NSKeyedArchiver:归档解档
新建模型类:Teacher, 必须遵守NSCoding协议, 里面有name和age属性,
实现协议方法:
- (void)encodeWithCoder:(NSCoder *)aCoder { //告诉系统需要归档哪些属性
[aCoder encodeObject:_name forKey:@"name"];
[aCoder encodeInt:_age forKey:@"age"];
}
- (instancetype)initWithCoder:(NSCoder*) aDecoder { //告诉系统解绑哪些属性,, 解析文件都会调用这个方法
if(self = [super init]) {
_name = [aDecoder decodeObjectForKey:@"name"];
_age = [aDecoder decodeIntForKey:@"age"];
}
return self;
}
归档:
Teacher * t = [[Teacher alloc] init];
t.name = @"aaa";
t.age = 18;
NSString * tempPath = NSTemporaryDirectory(); //获取temp目录, 也可以放到Documents的目录里面
NSString * filePath = [tempPath stringByAppendingPathComponent:@"teacher.data"];
[NSKeyedArchiver archiveRootObject:t toFile:filePath]; //归档, 会替换旧的数据
解档:
Teacher * t = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
只要在tableView中写一个代理方法,就可以实现对某一行左滑出现删除按钮的效果:
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
//点击删除时才会调用这个方法, 在这里删除数组的某一行,刷新列表
//[self.contacts removeObject:self.contacts[indexPath.row]];
[self.contacts removeObjectAtIndex:indexPath.row];
//[self.tableView reloadData];
[self.tableView deleteRowsAtIndexPaths@[ indexPath ] withRowAnimation:UITableViewRowAnimationLeft]; //UI删除某行,并且有动画, 可以同时删除多行
}
标签控制器: UITabBarController:
类似微信下方的导航条
高度是49
在AppDelegate.m中:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UITabBarController* tabbarController = [[UITabBarController alloc] init];
UIViewController * v1 = [[UIViewController alloc] init];
UIViewController * v2 = [[UIViewController alloc] init];
v1.tabBarItem.title = @"联系人"
v2.tabBarItem.title = @"消息"
v1.tabBarItem.image = [UIImage imageName:@"aaa"]
v2.tabBarItem.image = [UIImage imageName:@"bbb"]
v1.tabBarItem.badgeValue = @"9";
v2.tabBarItem.badgeValue = @"19";
v1.view.backgroundColor = [UIColor redColor];
v2.view.backgroundColor = [UIColor orangeColor];
[tabbarController addChildViewController:v1];
[tabbarController addChildViewController:v2];
self.window.rootViewController = tabbarController;
[self.window makeKeyAndVisible];
NSLog(@"%@", tabbarController.tabBar);
return YES;
}
QQ案例:
主流框架:TabBar-----Navigation----tableView(背景是灰色的是分组了,背景是一行一行的是没有分组的tableView)
拖一个TabBar控件, 在拖两个Navigation控制器, 拖两个线连接起来,线选viewController点, 每个Navigation下面拖一个TabView控件
A跳转到B, B却不需要显示TabBar底部的tab, 则勾选右侧属性:Hide Button Bar on Push
文字显示多行,只需要将右侧属性lines改成0
TableView拖一个UIView到最上面或者最下面出现黑线就是Header和footer
[imageView sizeToFit]; //让图片控件的大小跟图片一样大, 其他控件也可以类似使用达到控件大小和内容大小一样。
上一句可以这样: 选中图片控件---菜单Editor---Size to Fit Content ---可以让所有控件自适应。 ----有快捷键
将重写TableViewController时,无法显示静态单元格,因为默认重写的方法组个数和每组的行个数都是0, 所以删除这两个方法就行。
Modal 一个控制器跳转到另外一个控制器,两个控制器之间没有关系用Modal: 点击某个标题,如果需要登录,跳转到登录界面,用Modal,因为和上一个页面不存在关联关系
Push 一个控制器跳转到另外一个控制器, 两个控制器之间有关系用Push, 点击新闻的某一行,跳转新闻详情,用Push, 两个之间有关联
Modal使用:
Modal形式,A跳转到B
TestViewController * vc = [{TestViewController alloc} init];
vc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal; 页面切换效果,可以不加
[self presentViewControler:vc animated:YES completion:^ {
NSLog(@"跳转已经完成");
}]
Modal中在B控制器中关闭,退出A控制器:
[self dismissViewControllerAnimated:YES completion^ {
NSLog(@"已经关闭");
}]
modal拖线和push拖线都是使用show线, 然后在线的属性中Segue属性选择Modal, transtion属性:控制器之间的过渡效果
quart2d 二维绘图,类似canvas, 在CoreGraphhics框架中,以CG开头
1.获取图形上下文对象
2.向图形上下文对象中添加路径
3.渲染到对应设备上
图形上下文:CGContextRef
Bitmap Graphics Context
PDF Graphics Context
Window Graphics Context
Layer Graphics Context(UI控件上下文)
Printer Graphics Context
可以用C语言来绘图, 也可以用OC语言来绘图
拖一个UIView, 自定义类:HMView, 重写一个方法:
//C语言方式--直接在上下文上绘制
- (void)drawRect:(CGRect)rect {
CGContext ctx = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(ctx, 50, 50);
CGContextAddLineToPoint(ctx, 100, 100);
CGContextStrokePath(ctx);
}
//C语言方式2---现在路径上绘制,然后粘贴的哦上下文
- (void)drawRect:(CGRect)rect {
CGContext ctx = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 50, 50);
CGPathAddLineToPoint(path, NULL, 100, 100);
CGContextAddPath(ctx, path);
CGContextStrokePath(ctx);
}
//C + OC语言方式---oc转c
- (void)drawRect:(CGRect)rect {
CGContext ctx = UIGraphicsGetCurrentContext();
UIBezierPath * path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(50, 50)];
[path addLineToPoint:CGPointMake(100, 100)];
CGContextAddPath(ctx, path.CGPath); //将OC的path转成C的path
CGContextStrokePath(ctx);
}
//C + OC语言方式2---c转oc再转c
- (void)drawRect:(CGRect)rect {
CGContext ctx = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 50, 50);
CGPathAddLineToPoint(path, NULL, 100, 100);
UIBezierPath* path1 = [UIBezierPath bezierPathWithCGPath:path]; //c的path转OC的path
[path1 addLineToPoint:CGPointMake(150, 50)];
CGContextAddPath(ctx, path1.CGPath);
CGContextStrokePath(ctx);
}
//OC语言方式, 里面只实现c的部分功能, 某些功能还是要c语言 ----推荐
- (void)drawRect:(CGRect)rect {
UIBezierPath* path = [UIBezierPath bezierPath];
[path mvoeToPoint:CGPointMake(50, 50)];
[path addLineToPoint;CGPointMake(100, 100)];
[path stroke];
}
绘制要在drawRect方法中,系统会自动调用:
因为在这个方法中能获得正确的上下文
参数react是当前view的bounds
系统调用,第一次显示会调用,重绘也会调用。
调用view的setNeedsDisplay或者setNeedsDispalyInRect:inRect会重绘, inRect表示重绘的区域
drawRect不能手动调用。
练习
- (void)drawRect:(CGRect)rect {
UIBezierPath* path = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 100, 100)]; //矩形, 或者C语言的:CGContextAddRect
UIBezierPath* path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 100, 100, 100) cornerRadius:10]; //圆角矩形, 半径传50就是圆, 但是大于50的三分之二都会是圆。
UIBezierPath* path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 200, 100)]; //椭圆, 宽高一样就是圆, C语言:CGContextAddEllipseInRect
UIBezierPath* path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(150, 150) radius:100 startAngle:0 endAngle:2 * M_PI clockwise:1]; //通过画弧来画圆,clockwise:1表示顺时针
CGContextAddArc(ctx, 150, 150, 100, 0, 2 * M_PI, 0); //C语言画圆, 0表示顺时针
CGContextSetLineWidth(ctx, 10); //设置线宽
[path setLineWith:10] //OC
CGContextSetLineJoin(ctx, kCGLineJoinRound); //两条线连接处变圆弧,kCGLineJoinMiter:默认, kCGLineJoinRound圆角, kCGLineJoinBevel切角
[path setLineJoinStyle:kCGLineJoinRound];//OC
CGContextSetLineCap(ctx, kCGLineCapButt); //线段两头的样式:kCGLineCapButt,默认,kCGLineCapRound:圆角, kCGLineCapSquare:变短
[path setLineCapStyle:kCGLineCapRound]; //OC
CGContextSetRGBStrokeColor(ctx, 0.5, 1, 0.5, 1); //颜色值0-1
[[UIColor bluColor] setStroke]; //OC
//stroke描边, fill填充
CGContextClosePath(ctx); //起始点和终点用线连接, 关闭路径
[path closePath];// OC
CGContextFillPath(ctx); //填充
CGContextStrokePath(ctx);描边
[path stoke]; //OC
[path fill]; //OC
CGContextDrawPath(ctx, kCGPathStroke); //填充描边方式二:kCGPathStroke描边, kCGPathFill填充
[[UIColor redColor] setFill]; //填充的颜色
[[UIColor blueColor] setStock]; //填充的颜色
[path stoke];
[path fill]; //调用两句,即填充又描边
CGContextDrawPath(ctx, kCGPathFillStroke); //方式二:即填充又描边
[[UIColor greenColor] set]; //描边和填充都设置绿色,相同颜色,用set也可以
CGContextAddPath(ctx, path1.CGPath);
CGContextAddPath(ctx, path2.CGPath);
CGContextAddPath(ctx, path3.CGPath); 三个矩形,都部分相交
CGGontextDrawPath(ctx, kCGPathEOFill); //EO: 奇偶填充规则,相交的地方为奇数个填充,偶数个图形时不填充。
CGContextAddPath(ctx, path4.CGPath); //顺时针的外圆
CGContextAddPath(ctx, path5.CGPath); //逆时针的内圆
CGContextDrawPath(ctx, kCGPathFill); //最后得到圆环, 默认非零环绕数规则
path.usesEventOddFillRule = YES; //OC, 默认非零环绕数规则, YES是奇偶填充规则
NSArray* array = @[@0.3, @0.1, @0.2, @0.4]; //OC 数组, [array[0] floatValue] * 10, 计算用floatValue
画扇形: 先画圆弧,再向圆心连接一条线,只需要连一条, 然后fill填充,就得到扇形了。
//oc打印%字符串:%%,即两个百分号打印出一个百分号
//自定义View里面放一个子view, 对子view拖线无法拖到自定义view中设置控件名, 则我们对子view拖线到controller, 在将生成的代码剪切到自定义view中, 然后反向拖线,即从代码拖线到子view上。
//矩阵操作,旋转、缩放、平移, 这些操作放到CGContextAddXXXX之后,则没有效果,所以必须放到之前,可以一定义ctx,就做矩阵操作
CGContextRotateCTM(ctx, M_PI_4);//旋转
CGContextScaleCTM(ctx, 0.5, 0.5); //x,y轴分别缩放
CGContextTranslateCTM(ctx, 150, 150); //平移
[path stroke];
或者:CGContextStrokePath(ctx);
}
- (void)drawRect:(CGRect)rect {
----一条线路径,然后渲染
CGContextStrokePath(ctx);
----第二条线路径,然后渲染, 第一次渲染后,上下文路径清空,下面第二条线会重新绘制路径再渲染
CGContextStrokePath(ctx);
//图形上下文栈:只需要在渲染之前调用保存或恢复状态
CGContextSaveGState(ctx);//保存图形上下文颜色线宽等状态信息
CGContextRestoreGState(ctx); //栈顶出栈,即恢复之前保存的状态信息。
//xcode Product --- Analyze----右上角有一个蓝色小块---内存泄漏问题提示, 项目开发完后都要这样分析一下。
//quart2d内存管理
CGPathRelease(path); //上面有CGPathCreate***就在最后必须释放
CFRelease(path); //第二种释放方式
凡是在函数名字当中包含了retain、copy、create关键字,那么在调用完毕该函数,都要release释放。
CGXxxxxCreate对应的就是CGXxxxxRelease来释放, 通过CFRelease可以释放任何类型。
}
- (void)drawRect:(CGRect)rect {
NSString * str = @"aaaa";
[str drawAtPoint:CGPointZero withAttributes:nil]; 绘制文字
[str drawInRect:CGRectMake(0, 0, 200, 200) withAttributes:nil]; 绘制文字,可以换行
[str drawAtPoint:CPointMake(50, 50) withAttributes:nil]; 从某一点开始绘制
[str drawAtPoint:CPointMake(50, 50) withAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:30]}]; withAttributes参数可以是字体大小,文字颜色NSForegroundColorAttributeName等等很多属性。
//绘制图片, 大图和小图效果不一样
UIImage* image = [UIImage imageNamed:@"me"];
[image drawAtPoint:CGPointMake(50, 50)]; //绘制方式一
[image drawInRect:rect]; //绘制方式二, 将这个view填充图片---拉伸
[image drawAsPatternInRect:rect]; //绘制方式三, 图片平铺, 很小的纯色图片,平铺背景
UIImageView * imageView = [[UIImageView alloc] init]; //控件没有大小, 后面设置
imageView.frame = CGRectMake(0, 0, 200, 200);
imageView.image = [UIImage imageNamed:@"me"];
UIImageView * imageView = [[UIImageView alloc] initWithImage:[UIImage imagedNamed:@"me"]];// 控件大小默认图片大小
CGContextAddArc(ctx, 150, 150, 0, 2*M_PI, 1); 先绘制圆
CGContextClip(ctx); 裁剪
[image drawInRect:rect]; 绘制图片, 得到圆形图片
//绘制线,将绘制的上下文转图片填充到imageView
//UIGraphicsBeginImageContext(CGSizeMake(300,300)); 开启图片类型上下文, 等价于下面方法最后一个参数传1
UIGraphicsBeginImageContextWithOptions(CGSize(300, 300), NO, 2); //开启图片类型上下文方式二,--推荐, NO参数表示透明,一般NO, 2表示二倍图,保存图片会是600x600, 如果改成1,高清屏上会比较模糊,改成[UIScreen mainScreen].scale替换2, 或者0替换2也会自适应
CGContextRef ctx = UIGraphicsGetCurrentContext(); 获得当前上下文(图片类型)
CGContextMovePoint(ctx, 50, 50); //拼接路径
CGContextAddLineToPoint(ctx, 100, 100);
CGContextStrokePath(ctx); 渲染
UIIamage* image = UIGraphicsGetImageFromCurrentImageContext(); 通过图片类型的图形上下文 获取图片对象
UIGraphicsEndImageContext();关闭图片类型的图形上下文
self.imageView.image = image; //把获取的图片 放到 图片控件上。
//UIImageJPEGRepresentation(); 将image转jpeg的二进制数据
NSDate* data = UIImagePNGRepresentation(image); 将image转png的二进制数据
NSString* docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString* filePath = [docPath stringByAppendingPathComponent:@"xx.png"];
[data writeToFile:filePath atomically:YES]; //将图片二进制数据写入沙盒,则这个文件就是图片文件。
UIImageWriteToSavedPhotosAlbum(newImage, self, @select(image:didFinishSavingWithError:contextInfo), @"aaaaaa11111"); ///将图片保存到相册, select是保存完成的回调函数
//屏幕截图:
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self.view.layer renderInContext:ctx];
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(image, NULL, NULL,NULL);
}
-(void)image:(UIImage*)image didFinishSavingWithError:(NSError*)error contextInfo:(void*)contextInfo
{
//contextInfo 就是 aaaaaa11111
//这个回调函数不能随便写,只能这样写
}
//点击view的时候自动调用的函数
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
UISlider滑动条控件
触摸事件:
只有继承UIResponder的对象才能接收并处理事件,
UIApplication、UIViewController、UIView都继承UIResponder
触摸事件:
- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event
{
UITouch * t = touches.anyObject;
//t.tapCount ---快速点击的次数
//t.phasse ---触摸的阶段
//t.window ---触摸所在window
//t.view ---触摸的view
CGPoint p = [t locationInView:self]; //点击的坐标, 相对点击view的坐标
CGPoint p = [t locationInView:self.superview]; //相对父view的坐标
CGPoint p = [t previousLocationInView:self.superview]; //上一点的坐标
self.center = p; //控件的中心点坐标 等于 触摸点的坐标
//默认不支持多点触摸, 右侧属性勾选 Mutiple Touch打开多点触摸
touches在多点触摸时是一个结合:touches.count --触摸点的个数
for (UITouch* t in touches) {
}
右侧属性取消勾选:User Interaction Enable---则没有触摸事件,并且子控件也没有触摸事件
勾选隐藏或者透明度小于0.01时也没有触摸事件
勾选Clip Subviews, 如果子控件超过父控件,超过的地方会裁剪的, 裁剪掉的地方没有触摸事件,即使没有勾选,超出部分也没有触摸事件
}
- (void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event
{
}
- (void)touchesEnded:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event
{
}
- (void)touchesCancelled:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event
{
//例如滑动时,来电话时响应。
}
手势解锁 九宫格
给一个view设置图片背景可以用:backgroundColor--平铺的方式
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"home_refresh_bg"]];
- (void)awakeFromNib { //加载xib时调用,.xib和.storyboard文件加载都会调用这个
}
- (void)layoutSubviews //布局时回调,设置每个按钮的位置
{
}
右侧属性,背景颜色default表示没有颜色,表示null值,可能出现奇葩的颜色, clear color是透明色,只是0 0 0 0;
[btn setUserInteractinoEnabled:NO]; 设置按钮没有点击效果
CGRectContainsPoint(btn.frame, p); //返回触摸点是否在btn内, p是触摸点
手势识别:---借助UIGestureRecoginer类
UITapGestureRecognier --敲击
UILongPressGestureRecognizer --长按
UISwipeGestureRecognizer --轻扫
UIRotationGestureRecognizer ---旋转
UIPinchGestureRecognizer ---捏合
UIPanGestureRecognizer ---拖拽
//1 创建手势对象
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
tap.numberOfTapsRequired = 2; 表示双击才会响应这个手势
tap.numberOfTouchesRequired = 2; //双指点击才响应
//2 对某个view添加手势
[self.imageView addGestureRecognizer:tap];
//3 实现手势方法
- (void)tap:(UITapGestureRecognizer*) sender
{
NSLog(@"使用了手势"); //类似图片的点击事件
//如果是长按的方法,长按和长按后移动都会调用多次这个方法,怎么过滤掉移动的呢?
if (send.state == UIGestureRecognizerStateBegan) {
只响应第一次长按的回调
}
}
UILongPressGestureRecognizer* longPress;
longPress.minimumPressDuration = 3; //至少长按3秒, 默认0.5秒
longPress.allowableMovement = 50; //长按时,并且手指移动的半径,都响应长按
UISwipeGestureRecognizer* swipe; //默认的手势是从右往左滑才会响应,其他方向不响应
swipe.direction = UISwipeGestureRecognizerDirectionLeft; //添加从左往右扫的手势, 当然还可以添加上下扫
如果左右手势都加,则:[self.imageView addGestureRecognizer:swipe];[self.imageView addGestureRecognizer:swipe1]; 加swipe和swipe1两个
UIRotationGestureRecognizer* rotation //两个手指才是旋转
- (void)rotation:(UIRotationGestureRecognizer*)sender
{
//self.imageView.transform = CGAfineTransformMakeRotation(sender.rotation); 不能用这种方法旋转,因为第一次旋转没有问题,第二次旋转会恢复到原始位置再旋转
slef.imageView.transform = CGAffineTransformRotate(slef.imageView.transform, sender.rotation); //也有问题,这个角度是累加的角度,旋转速度非常快
sender.rotation = 0; //必须加这一句,不让角度累加,每次都是实际角度
}
UIPinchGestureRecognizer* pinch //捏合
CGAffineTransformScale---缩放,并且sender.scale = 1; 重置缩放,不让缩放累加
UIPanGestureRecognizer * pan //拖拽
CGAffineTransformTranslate ---平移
CGPoint p = [sender translationInView:sender.view];
[sender setTranslation:CGPointZero inView:sender.view]; //重置位置,不让平移累加
手势冲突:即多个手势一起使用:添加代理:UIGestureRecognizerDelegate
rotation.delegate = self;
pinch.delegate = self; //当然这两个只设置一个也行
代理方法:
-(BOOL)getstureRecognizer:(UIGestureRecognizer*)gestureRecognizer shuldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
{
return YES;
}
CALayer: UIView本身不具备显示功能,是它内部的层CALayer才有显示功能,才有绘制的功能。
redView.layer.borderWidth = 10; //设置边框是10
redView.layer.borderColor = [UIColor grayColor].CGColor; //边框颜色
redView.layer.shadowOffset = CGSizeMake(10, 10); //设置阴影
redView.layer.shadowColor = [UIColor blueColor].CGColor: //阴影颜色
redView.layer.shadowOpacity = 0.5; //阴影默认不显示,设置透明度才会显示
redView.layer.shadowRadius = 50; 阴影半径
redView.layer.cornerRadius = 50; //圆角半径
redView.layer.masksToBounds = YES; //超出范围不显示
redView.layer.bounds = CGRectMake(0, 0, 200, 200); //设置控件的大小
redView.layer.position = CGPointMake(0, 0); //控件中心点位置
redView.layer.anchorPoint = CGPointMake(0.5, 0.5); //锚点,需要配合position一起用, position是(100, 100), 如果anchorPoint是(0, 0),则最终左上角位置是(100,100),anchorPoint(0.5, 0.5),则最终中心点位置是(100,100),anchorPoint(1,1)则最终右下角位置是(100,100);
//redView.layer.frame = CGRectMake(0, 0); 不推荐这样写
redView.layer.contents = (__bridge_id_Nullable)([UIImage imageNamed:@"me"].CGImage); //对报错回车就自动加上了__bridge_id_Nullable(c转OC), 给控件里面添加图片
手动创建layer:
CALayer* layer = [[CALayer alloc] init];
layer.backgroundColor = [UIColor redColor].CGColor;
layer.position = CGPointMake(200, 200);
layer.bounds = CGRectMake(0, 0, 100, 100);
[self.view.layer addSublayer:layer];
//layer的隐式动画, 只有属性的注释上有Animatable就是有隐式动画, UIView---layer1---layer2, 控件的根layer没有隐式动画,即layer1没有隐式动画,layer2有
self.layer.position = CGPointMake(300, 200); //点击时,layer的位置修改了,会有动画效果,即隐式动画
//禁用隐式动画(事务)
[CATransaction begin];
[CATransaction setDisableActions:YES];
[CATransaction commit];
旋转:
self.layer.transform = CATransform3DMakeRotation(M_PI_4, 1, 0, 0);
self.layer.transform = CATransform3DRotate(self.layer.transform, M_PI_4, 1, 0, 0); 1表示沿着x轴旋转
缩放
self.layer.transform = CATransform3DScale(self.layer.transform, 0.5, 0, 0); x轴缩放0.5
平移
self.layer.transform = CATransform3dTranslate(self.layer.transform, 10, 0, 0); x轴平移10
类似view的平移缩放旋转:
self.layer.affineTransform = CGAffineTransformRotate(self.layer.affineTransform, M_PI_4);
//计时器,每秒运行异常函数
[NSTimer scheduledTiimerWithTimeInterval:1 target:self selector:@selector(timeChange) userInfo:nil repeats:YES];
NSDate * date = [NSDate date]; //获取当前时间
NSDateFormatter 可以对上面日期格式化,比如获得秒。
NSCalendar * cal = [NSCalendar currentCalendar]; //获得日历,然后去获得日期和时间
CADisplayLink
是一种以屏幕刷新率触发的时钟机制, 每秒种执行大约60次左右。
CADisplayLink * link = [CADisplayLink displayLinkWithTarget:self selector:@selector(timeChange)];
[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
核心动画:在线程操作,不会阻塞主线程。作用于CALayer
CAAnimation, 有很多子类。
基本动画:
CABasicAnimation * anim = [[CABasicAnimation alloc] init];
anim.keyPath = @"position.x";
anim.fromValue = @(10);
anim.toValue = @(300); //到300的位置
anim.fillMode = kCAFillModeForwards; //动画完成后默认返回开始位置,增加这两句代码,则会停留在最后位置
anim.removedOnCompletion = NO;
//anim.byValue = @(10) 表示从初始位置增加10
anim.duration = 5; //动画持续的时间
anim.repeatCount = INT_MAX; //无限重复,重复次数
[self.layer addAnimation.anim forKey:nil];
关键帧动画
CAKeyframeAnimation *anim = [[CAKeyframeAnimation alloc] init];
anim.keyPath = @"position";
NSValue* v1 = [NSValue valueWithCGPoint:CGPointMake(100,100)];
NSValue* v2 = [NSValue valueWithCGPoint:CGPointMake(500,500)];
NSValue* v3 = [NSValue valueWithCGPoint:CGPointMake(300,300)];
anim.values = @[v1, v2, v3]; //动画经过数组中的点
或者通过path动画
//UIBezierPath* path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(150,150) radius:100 startAngle:0 endAngle:2*M_PI clockwise:1];
//anim.path = path.CGPath; 绕着圆路径动画
[self.layer addAnimation:anim forKey:nil];
组动画
CAAnimationGroup* group = [CAAnimationGroup alloc] init];
CABasicAnimation * anim ----上面的普通动画
CAKeyframeAnimation *anim2 ----上面的关键帧对话
group.animations = @[anim, anim2];
[self.layer addAnimation:group forKey:nil];
转场动画:
右侧属性,有一个手势选项,里面有很多手势,可以拖一个手势进入图片
左边控件树就出现手势,对手势拖线得到手势执行方法
选中手势,属性,选择手势方向
- (IBAction)imageChange:(UISwipeGestureRecognizer*)sender
{
if(sender.direction == UISwipeGestureRecognizerDirectionLeft) {
//从右往左滑
}else if(sender.direction == UISwipeGestureRecognizerDirectionRight) {
//从左往右滑
}
}
self.imageView.image = [UIImage imageNamed:@"1.png"];
CATransition * anim = [[CATransition alloc] init];
anim.type = @"cube"; //有很多各种动画类型
anim.subtype = kCATransitionFromFight; //动画方向,从左往右
[self.imageView.layer addAnimation:anim forKey:nil];
画板案例:
手动调用滑动条改变回调函数
[self lineColorChange:self.firstButton]; 调用函数的sender用self.firstButton
橡皮:将画笔改成背景色,然后绘制直线。
UIDynamic: 动力学:重力、碰撞、悬挂等
重力:
@propety (nomatomic, strong) UIDynamicAnimator *animator;
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; //根据某一个范围 创建动画者对象, 这个对象必须提出到属性中,
UIGravityBehavior* gravity = [[UIGravityBehavior alloc] initWithItems:@[self.redView]]; //根据某一个动力学元素 创建行为
gravity.angle = M_PI; //重力方向
gravity.gravityDirection = CGVectorMake(1,1); //向量方向, 根据顺序会覆盖上面一句的效果
gravity.magnitude = 10; //量级,即下落速度
UICollisionBehavor * collision = [[UICollisionBehavior alloc] initWithItems:@[self.redView]]; //碰撞行为, 如果有多个物体碰撞改成:@[self.redView, self.bluView];
collision.translatesReferenceBoundsIntoBoundary = YES; 设置碰撞为边界碰撞
collision.collisionMode = UIColisionBehaviorModeEverything; // 默认的碰撞模式item和边界都碰撞, 还有:UICollisionBehaviorModeItem只item碰撞、UICollisionBehaviorModeBoundaries只边界碰撞;
//[collision addBoundaryWithIdentifier:@"key" fromPoint:CGPointMake(0, 200) toPoint:CGPointMake(200, 250)]; //创建一条边界
//UIBezierPath* path = [UIBezierPath bezierPathWithRect:self.blueView.frame];
//[collision addBoundaryWithIdentifier:@"key" forPath:path]; //创建边界方式二
[self.animator addBehavior:gravity]; //把行为添加到动画者当中
[self.animator addBehavior:collosion];
碰撞:
上面代码是在重力行为的基础上加了碰撞行为
甩行为:UISnapBehavior;
- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event
{
UITouch* t = touches.anyObject;
CGPoint p = [t locationInView:view];
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UISnapBehavior* snap = [[UISnapBehavior alloc] initWithItem:self.redView snapToPoint:p];
snap.damping = 0.5; //动画速度0-1之间
[self.animator addBehavior:snap];
}
附着行为:
刚性附着:
弹性附着:
- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event
{
UITouch* t = touches.anyObject;
CGPoint p = [t locationInView:view];
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
self.attach = [[UIAttachmentBehavior* alloc] initWithItem:self.redView attachedToAnchor:p];
//self.attach.lenght = 100; 可以设置一个固定长度
//self.attach.damping = 0.5;
//self.attach.frequency = 2; 加上这两句变成弹性附着
[self.animator addBehavior:self.attach];
}
-(void)touchesMoved:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event
{
self.attach.anchorPoint = p;
}
推行为:
- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event
{
UITouch* t = touches.anyObject;
CGPoint p = [t locationInView:view];
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UIPushBehavior *push = [[UIPushBehavior alloc] initWithItem:@[self.redView] mode:UIPushBehaviorModeContinuous ];
push.magnitude = 1;
push.pushDirection = CGVectorMake(1,1);推的方向
push.angle = M_PI; //也可以设置推的方向
[self.animator addBehavior:*push];
}
动力学元素自身属性:
UIDynamicItemBehavior* itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.blueView]];
itemBehavior.elasticity = 1;//弹力
itemBehavior.density = 0.5;// 密度
itemBehavior.friction = 20; //摩擦力
[self.animator removeBehavior:self.att]; //移除行为
大转盘和彩票项目:
设置大背景一般用background或者layer来做:
self.view.layer.contents = (__bridge id)[UIImage imageNamed:@"LuckyBackground"].CGImage;
//根据大图,切割出来一部分, 即精灵图
- (UIImage*) clipImageWithImage:(UIImage*)image withIndex:(NSInteger)index
{
CGFloat w = image.size.width / 12 * [UIScreen mainScreen].scale;
CGFloat h = image.size.height * [UIScreen mainScreen].scale; //乘于不同机型屏幕的缩放因子
CGFloat x = index * w;
CGFloat y = 0;
CGImageRef imageRef = CGImageCreateWithImageInRect(image.CGImage, CGRectMake(x, y, w, h));
return [[UIImage alloc] initWithCGImage:imageRef scale:2.3 orientation:UIImageOrientationUp];
}
layer的动画结束后会回到起始位置,可以是设置保留到结束位置
动画结束后,只有点击起始位置才会响应事件,结束位置不会响应, 解决方式是将旋转方法改成旋转到方法。
启动图片如果不是用xib, 则启动图片的大小决定应用的宽度,比如,xib则屏幕宽时375, 但是用图片,图片宽320,则应用的宽度也会是320.解决方法:放全各种规格的图片。不要缺少某种大小的图片。
PrefixHeader.pch文件, .pch文件是一个项目的全局文件,经常要用的函数或者变量放到里面,要让整个项目可以用,
需要配置:在项目配置中:搜索head, 找Prefix Header, 里面输入1,报错,分别复制报错信息和.pch文件的路径, 这两个路径不同的部分替换掉1.
#define kScreenSize [UIScreen mainScreen].bounds
#define kScreenWidth [UIScreen mainScreen].bounds.size.width
#define kScreenHeight [UIScreen mainScreen].bounds.size.height
则其他文件可以直接用kScreenSize.
文件夹属性:Class Prefix: CYH, 则每新建一个类都有前缀, 一般三位或三位以上
取消按钮高亮状态:重新UIButton, 重写下面方法,注释调用父类的语句
- (void)setHighlighted:(BOOL)highlighted
{
// [super setHighlighted:highlighted];
}
自定义View调用Controller方法,有两种方法:代理和block方式
很多按钮,默认选择其中一个按钮,只需要调用那个按钮的点击方法就行, 参数sender传按钮本身
if(i == 0) {
[self tabBarButtonClick:tabbarButton];
}
如何解决图片变成蓝色?渲染时不要改变颜色,用图片的原始颜色
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
图片控件不能点击,如果需要点击需要设置:
imageView.userInteractionEnabled = YES;
父控件如果是透明的,添加的子控件也会是透明的,所以子控件不要添加到透明的父控件,而是应该添加到其他的controller控件中。
Class clz = NSClassFromString(@"UISwitch"); //将一个UI类型的字符串转成一个类型
String str = NSStringFromClass(clz); //将一个类型转成字符串表示
UISwitch * switcher = [[clz alloc] init]; //UISwitch可以改成UIView或者id
UIView * obj = [[clz alloc] init];
if([obj isKindOfClass:[UIImageView class]]) { //判断类型
UIImageView* imageView = (UIImageView*) obj;
imageView.image = [UIImage imageNamed:@"arrow_right"];
[imageView sizeToFit];
}
以前都是获得nib中的view, 如何获得Nib呢?
UINib * nib = [UINib nibWithNibName:@"HMProductCell" bundle:nil);
UIClollectionView是九宫格view
将字符串转成方法名字
NSString nstr = @"funName";
SEL funName = NSSelectorFromString(nstr);
[self performSelector:@selector(funName)];
切换window的根控制器
UIWindow* win = [UIApplication shareApplication].keyWindow;
win.rootViewController = tab;
UIWebView:
UIWebView* webView = (UIWebView*)self.view;
NSURL * url = [NSURL URLWithString:@"http://www.baidu.com"];
NSURLRequest* req = [NSURLRequest requestWithURL:url];
[webView loadRequest:req];
本地文件路径转URL
NSURL* url = [[NSBundle mainBundle] URLForResource:@"help.html" withExtension:nil];
ios默认的请求都必须https, 不支持http
国际化:
应用程序名字:Info.plist中:Bundle display name: 微博, 如果没有这个字段,就添加,然后下拉选择这个字段
1. 添加程序对语言的支持
2. 创建文件InfoPlist.strings
3. 点击文件,右边打钩
4.展开箭头, 找到对应的中英文文件
5. 写key和value; 如:"CFBundleDisplayName"="wechat";
storyboard的中英文:
1. 添加程序对语言的支持
2. 会有提示 勾选 sb
3. 点击文件 右侧打钩
4. 展开 找到对应中英文 strings 文件, 此时,系统会自动生成 key value
5. 改 value, key不要动。
6. 如果又添加了文字,则取消勾选再勾选一下。
代码国际化:
1. 添加程序对语言的支持
2. 创建一个Localizable.strings文件
3. 点击文件, 右边打钩
4. 展开箭头 中英文 key value
5. key 随便写, 保证使用的时候 是这个唯一的key就可以了
6. value 中文写中文,英文写英文
7. 在用代码使用字符串的时候,使用 系统提供的NSLocalizedString()
"title" = "标题1";
NSString* title= NSLocalizedString(@"title", nil);
应用内国际化:
NSString* title = NSLocalizedStringFromTable(@"title", @"lan_en", nil);
1. 新建多个strings的文件, 文件名随便起
2. 写 key value
3. 使用系统提供的NSLocalizedStringFromTable(@"key", @"文件名字", nil); 会把对应文件中key所对应的value返回给你。
上架:
开发者账户类型
1. 个人账户--99美元---能上架,给钱就能用
2. 企业 --- 99美元 ---能上架 团队开发 需要“邓白氏”认证 ---一般是这个,我们自己不是开发者,企业会邀请我们成为开发者。
3. 商业 --299美元, 不能上架, 但是不向apple store提交应用。不需要审核, 可以在点餐系统这样几家店内部使用。
真机调试:需要开发者账号才能真机调试
打电话:
UIApplication * app = [UIApplication shareApplication];
NSURL * url = [NSURL URLWithString:@"tel://10086"); //@"sms://10086"是发短信, 如果是调到appStore,则搜索网易彩票appstore, 有一个网页地址开头:https://itunes.apple.com
[app openURL:url];
github中IOS-System-Services框架是一个获取手机硬件信息的框架。
应用打包
应用上传
应用发布
多线程:
后台执行方法:
[self performSelectorInBackground:@selector(demo) withObject:nil];
多线程创建:
pthread C语言 程序员自己管理 几乎不用
NSTread OC语言 程序员自己管理 偶尔使用
GCD C语言 自动管理 经常使用
NSOperation OC语言 自动管理 经常使用
pthread的使用:
#import <pthread/pthread.h>
- (void)viewDidLoad {
[super viewDidLoad];
//第一个参数 线程编号的地址
//第二个参数 线程的属性
//第三个参数 线程要执行的函数void*
//第四个参数 要执行的函数的参数
//返回值:0:成功, 非0:失败
pthread_t pthread;
int result = pthread_create(&pthread, null, demo, null);
//char * name = "zs";
//NSString * n2 = @"ls";
//int result = pthread_create(&pthread, null, demo, name); //带参数,参数传到demo函数中
//int result = pthread_create(&pthread, null, demo, (__bridge void *)(n2)); //OC字符串转C字符串的函数参数
NSLog(@"main %@", [NSThread currentThread]); //主线程
if(result == 0) {
NSLog(@"成功");
}else {
NSLog(@"失败");
}
}
void *demo(void *param){
NSLog(@"hello %@", [NSThread currentThread]); //子线程, 线程执行的方法,耗时操作, 打印出number不等于1,就是子线程
NSString *name = (__bridge NSString *)(param);
return NULL;
}
把OC中的对象,传递给c语言的函数, 要桥接
NSThread方式:
- (void)viewDidLoad {
[super viewDidLoad];
//方式1:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
[thread start];
//方式2:会立即自动执行
[NSThread detachNewThreadSelector:@selector(demo) toTarget:self withObject:nil];
//方式3:
[self performSelectorInBackground:@selector(demo) withObject:nil];
//方式1:带参数
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:@"tom"];
thread.name = @"t1"; //设置线程名字
thread.threadPriority = 1; 取值0-1, 默认0.5, 设置多个线程的执行优先级,只保证几率大,不保证先执行完。
[thread start]; //线程使用后不能再次使用
}
- (void) demo {
NSLog(@"hello %@", [NSThread currentThread]);
}
- (void) demo2:(NSString *) name {
NSLog(@"hello %@-----@", [NSThread currentThread], name);
[NSThread sleepForTimeInterval:3]; //阻塞3秒钟
@synchronized(self) { //加锁,防止多个线程修改同一个对象, self可以改成任何对象
}
[NSThread exit];//线程手动退出
}
原子属性:
@property (atomic, copy) NSString *name; //atomic: 原子性,线程安全, nonatomic: 非原子性,线程不安全, 不写默认是原子属性
@synthesize name = _name; //如果同时又name()和getName()方法,则不会自动生成_name属性,需要这样显示声明_name属性
原子性只对一段代码只有读或者只有写的情况有效,如果既有读又有写,一样会线程出现问题。
@property (nonatomic, weak) UIScrollView *scrollView; //UI控件需要用weak或strong修饰
几乎所有UIKit提供的类都是线程不安全的,所有更新UI的操作都在主线程上执行
所有包含Mutable的类都是线程不安全的。
[self.imageView sizeToFit]; //这样不需要设置imageView的大小,大小和图片大小一样大
子线程调用, 转到调用主线程的方法,即子线程转主线程:
[self performSelectorMainThread:@selector(updateUI:) withObject:img waitUntilDone:YES]; //最后一个参数:YES: updateUI方法执行完,再执行下面的方法, NO: 不等待主线程的方法,子线程直接往下执行
使用http请求图片报错,需要https, 在Info.plist配置一下就支持http
OC对象用Strong
UI控件如果是拖线拖控件用weak, 如果是自己创建的UI控件用strong
@autoreleasepool{} 自动释放池,大括号里面的对象会自动释放。
for(int i = 0; i < 1000000; i++) {
@autoreleasepool { //大量循环是最好加上自动释放池,否则内存消耗很大,内存释放池,大括号结束就会释放里面的内存
NSString *str = [NSString stringWithFormat: @"hell %d", i];
}
}
属性修饰符:
retain: 只有mrc中使用
strong: 只有arc中使用,修饰对象,强引用 普通对象用strong
weak: 只有arc中使用,修饰对象,弱引用,delegate需要用weak修饰,@property (nonatomic weak) id delegate;
assign:arc和mrc都可用,修饰基本类型
copy: arc和mrc都可用, 一般用于字符串,复制一份再赋值, 如果将copy改成strong, 则字符串赋值,在改变原字符串变量,新变量也会改变。
block类型也要用copy,
定时器:
NSTimer *timer = [NSTimer timerWithTimeInteval:1.0 target:self selector:@selector(demo) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultLoopModel]; //NSDefaultLoopModel会被其他事件阻塞, NSRunLoopCommonModes不会被其他事件阻塞。
消息循环是在一个指定的模式下运行的,设置的输入事件也需要指定一个模式,消息循环的模式必须和输入事件的模式匹配才会执行。
NSLog(@"hello %@", [NSRunLoop currentRunLoop].currentMode); 打印当前消息循环的模式
Xcode没有提示:1.编译一下, 2.关闭再开启xcode
GCD类似线程池:
//创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//创建任务
dispatch_block_t task = ^{
NSLog(@"hello %@", [NSThread currentThread]); //子线程
dispatch_sync(dispatch_get_main_queue(), ^{
//从子线程回到主线程
})
}
//异步执行, 从线程池中开启子线程
dispatch_async(queue, task); //同步执行可以用dispatch_sync(), GCD一般用异步
串行队列, 同步执行, 不开新线程, 任务串行执行
dispatch_queue_t queue = disatch_queue_create("hm", DISPATCH_QUEUE_SERIAL);
for(int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"hello %d %@", i, [NSThread currentThread]);
});
}
串行队列, 异步执行, 开新线程(只新增了一个线程), 任务串行执行
dispatch_queue_t queue = disatch_queue_create("hm", DISPATCH_QUEUE_SERIAL);
for(int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"hello %d %@", i, [NSThread currentThread]);
});
}
并行队列, 同步执行, 不开新线程, 任务按顺序执行----跟串行队列, 同步执行效果一样, 很少用
dispatch_queue_t queue = disatch_queue_create("hm", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"hello %d %@", i, [NSThread currentThread]);
});
}
并行队列, 异步执行, 开新线程(新增多个线程), 任务并行执行,无序执行
dispatch_queue_t queue = disatch_queue_create("hm", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"hello %d %@", i, [NSThread currentThread]);
});
}
主队列, 异步执行, 在主线程,顺序执行
主队列的特点:先执行完主线程的代码,才会执行主队列中的任务。
for(int i = 0; i < 10; i++){
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"hello %d %@", i, [NSThread currentThread]);
})
}
主队列, 同步执行, 出现死锁,主队列等主线程,主线程等主队列,所有死锁---主线程上执行才会死锁
for(int i = 0; i < 10; i++){
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"hello %d %@", i, [NSThread currentThread]);
})
}
解决上面的死锁:
dispatch_async(dispatch_get_global_queue(0, 0), ^{ //全局队列,也是在子线程,和并发队列的区别是不能设置名字
for(int i = 0; i < 10; i++){
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"hello %d %@", i, [NSThread currentThread]);
})
}
})
dispatch_get_global_queue(0, 0) 参数一是优先级,参数二没有作用,直接传0
Barrier阻塞:主要用于在多个异步操作完成后,统一对非线程安全的对象进行更新。
适合于大规模的I/O操作
当访问数据库或文件的时候,更新数据的时候不能和其他更新或读取的操作在同一时间执行,可以使用调度组,不过有点复杂,可以使用:dispatch_barrier_assync解决。
dispatch_barrier_assync(dispatch_get_global_queue(0, 0), ^{
这里面相当于加了一把锁,外面是多线程, 里面同时操作一个变量,保证线程安全
//等待队列中所有的任务都执行完,才会执行这个代码
})
NSLog("%zd", a); //zd表示既可以是int,也可以是long, 或者他们的无符号类型
//延迟执行, 第一个参数:精度到纳秒, 第二个参数:线程, 第三个参数:任务
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{});
//一次性执行, 在当前线程执行
dispatch_onece()
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/cyhgyq/test-ios.git
git@gitee.com:cyhgyq/test-ios.git
cyhgyq
test-ios
testIos
master

搜索帮助