iOS开发之路--微博骨架搭建

时间:2022-05-07 13:52:08

最终效果图:

iOS开发之路--微博骨架搭建

beyondviewcontroller.m

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
//
// beyondviewcontroller.m
// 20_帅哥no微博
//
// created by beyond on 14-8-3.
// copyright (c) 2014年 com.beyond. all rights reserved.
// 这个就是主控制器,分为两块,下面是dock栏,上面是显示不同的子控制器的view,子控制器最好用导航控制器包装一下,这样子控制器就自带了导航条,左按钮,标题,右按钮
 
/*
 无法点击,或点击 无响应的原因:
 userinteractionenabled = no;
 hidden = yes
 alpha <= 0.01
 clearcolor ,view的颜色为透明,不可以被点击
 */
 
#import "beyondviewcontroller.h"
#import "dock.h"
#import "dockbtn.h"
#import "column.h"
// 主控制器下面dock的高度
#define kdockheight 44
@interface beyondviewcontroller ()
{
  // 从plist中加载 的栏目对象数组
  nsmutablearray *_columns;
  
  // 主控制器下方的dock选项栏
  dock *_dock;
  
  // 记录当前选中的子控制器,目的是将其view从父控制器的view中移除,为添加新的子控制器的view做准备
  uiviewcontroller *_currentchildvc;
}
@end
 
@implementation beyondviewcontroller
 
- (bool)prefersstatusbarhidden
{
  return no;
  
}
 
- (void)viewdidload
{
  [super viewdidload];
 
  // 0.从plist加载 栏目数组,遍历数组,根据字典,生成一个一个栏目对象,存入栏目对象数组中
  _columns = [nsmutablearray array];
  nsbundle *mainbundle = [nsbundle mainbundle];
  nsstring *fullpath = [mainbundle pathforresource:@"columnlist.plist" oftype:nil];
  nsarray *arr = [nsarray arraywithcontentsoffile:fullpath];
  
  for (nsdictionary *dict in arr) {
    column *column = [column columnwithdict:dict];
    [_columns addobject:column];
  }
  // 1.添加dock到主控制器方的下方
  [self adddock];
  
  // 2.一次性创建所有的子控制器,并用导航包装后,添加到当前控制器的childviewcontrollers
  [self createallchildviewcontrollers];
  
  // 3.默认选中第0个控制器
  [self changechildviewatindex:0 andchildvcclassname:@"homeviewcontroller"];
  
  // 4.一次性设置全局的导航栏上面的颜色主题样式
  [self setglobalnavigationitemcolortheme];
  
}
#pragma mark 添加dock
- (void)adddock
{
  // 1.添加dock到主控制器方的下方
  _dock = [[dock alloc] init];
  
  // 2.监听dock内部btn的点击,让控制器成为dock的代理属性,或者,为dock的成员blok赋值
  __unsafe_unretained beyondviewcontroller *beyond = self;
  _dock.btnclickblock = ^(dockbtn *btn)
  {
    // 调用自定义方法,更改子视图,参数1:索引号,参数2:子控制器的类名
    [beyond changechildviewatindex:btn.tag andchildvcclassname:btn.viewcontrollerclassname];
  };
  
  
  // 3,设置dock的frame
  _dock.frame = cgrectmake(0, self.view.frame.size.height - kdockheight, self.view.frame.size.width, kdockheight);
  log(@"_dock frame--%@",nsstringfromcgrect(_dock.frame));
  // 4,添加dock到主控制器方的view
  [self.view addsubview:_dock];
  
  // 2.遍历column对象数组,批量添加dock里面的dockbtn
  for (column *column in _columns) {
    [_dock adddockbtnwithiconname:column.columnimgname title:column.columnname viewcontrollerclassname:column.columnclassname];
  }
  
  // 3.设置dock默认选中第0个
  [_dock setdockbtnclickedatindex:0];
  
}
// 自定义方法,更改子视图,参数1:索引号,参数2:子控制器的类名
- (void)changechildviewatindex:(int)index andchildvcclassname:(nsstring *)viewcontrollerclassname
{
  log(@"点击了 %@",viewcontrollerclassname);
  if (self.childviewcontrollers.count > 0) {
    // 0,先取出新的子控制器,如果 新的子控制器就是当前的这个控制器,直接返回 好吗?
    uiviewcontroller *childvc = [self childviewcontrollers][index];
    if (childvc == _currentchildvc) return ;
    // 1,先移除先前的子控制器的view
    [_currentchildvc.view removefromsuperview];
    
    // 2,添加新的子控制器的view到主控制器的view
    
    childvc.view.frame = cgrectmake(0, 20, 320, 416);
    //log(@"self view --%@",nsstringfromcgrect(self.view.frame));
    //log(@"childvc view --%@",nsstringfromcgrect(childvc.view.frame));
    // 不会重复添加view,因为一旦发现重复添加某个view,就会将它置于最上面,最好是,先移除旧的view,再添加新的view
    [self.view addsubview:childvc.view  ];
    
    // 3,重要,必须更新当前的子控制器,为下次移除做准备
    _currentchildvc = childvc;
  }
}
 
#pragma mark 创建所有的子控制器(一共5个,首面,消息,我,广场,更多)
- (void)createallchildviewcontrollers
{
  // 1.遍历栏目对象数组,批量创建所有的子控制器,并用导航控制器包装,最后添加到self childviewcontrollers数组中保存
  for (column *column in _columns) {
    class c = nsclassfromstring(column.columnclassname);
    uiviewcontroller *childvc =nil;
    if ([nsstringfromclass(c) isequaltostring:@"moreviewcontroller"]) {
      // 特别注意:在继承了tableview之后,要想再使用group样式,必须在创建的时候指定样式为group,这儿特别指的是moreviewcontroller
      childvc = [[c alloc]initwithstyle:uitableviewstylegrouped];
    } else {
      childvc = [[c alloc]init];
    }
    // 设置导航栏的标题
    childvc.navigationitem.title = column.columnname;
    // 重写父类的方法
    [self addchildviewcontroller:childvc];
  }
}
#pragma marck - 重写父类的方法
// 为了在添加子控制器时,全部包装成一个个导航控制器,所以重写addchildviewcontroller方法
- (void)addchildviewcontroller:(uiviewcontroller *)childvc
{
  uinavigationcontroller *nav = [[uinavigationcontroller alloc]initwithrootviewcontroller:childvc];
  // 将包装成导航控制器的子控制器添加到主控制器中,这样每一个子控制器就拥有自己的特有的导航条了
  [super addchildviewcontroller:nav];
}
 
 
// 4.一次性设置全局的导航栏上面的颜色主题样式
- (void)setglobalnavigationitemcolortheme
{
  // 1.导航栏
  // 1.1.操作navbar相当操作整个应用中的所有导航栏
  uinavigationbar *navbar = [uinavigationbar appearance];
  
  // 1.2.设置导航栏uinavigationbar的背景图片(拉伸)
  [navbar setbackgroundimage:[uiimage imagestretchedwithname:@"navigationbar_background.png"] forbarmetrics:uibarmetricsdefault];
  // 1.3.设置状态栏背景,没有效果???
  [uiapplication sharedapplication].statusbarstyle = uistatusbarstylelightcontent;
  
  
  // 1.4.设置导航栏uinavigationbar的title文字属性,通过字典 设置
  nsmutabledictionary *navigationbartitledict = [nsmutabledictionary dictionary];
  // 前景色,即文字的颜色
  [navigationbartitledict setobject:[uicolor darkgraycolor] forkey:nsforegroundcolorattributename];
  // 文字阴影取消,字典中不能放结构体,要用nsvalue包装一下
  [navigationbartitledict setobject:[nsvalue valuewithuioffset:uioffsetzero] forkey:nsshadowattributename];
  
  
  // 2.导航栏上面的item
  uibarbuttonitem *barbtnitem =[uibarbuttonitem appearance];
  // 2.1.设置背景
  // 按钮正常状态时侯的背景
  [barbtnitem setbackgroundimage:[uiimage imagenamed:@"navigationbar_button_background.png"] forstate:uicontrolstatenormal barmetrics:uibarmetricsdefault];
  // 按钮高亮状态时侯的背景
  [barbtnitem setbackgroundimage:[uiimage imagenamed:@"navigationbar_button_background_pushed.png"] forstate:uicontrolstatehighlighted barmetrics:uibarmetricsdefault];
  // 按钮未选中状态时侯的背景
  [barbtnitem setbackgroundimage:[uiimage imagenamed:@"navigationbar_button_background_disable.png"] forstate:uicontrolstatedisabled barmetrics:uibarmetricsdefault];
  
  
  // 2.2.设置barbtnitem的文字属性
  nsmutabledictionary *baritemtitledict = [nsmutabledictionary dictionary];
  // baritemdict的文字颜色
  [baritemtitledict setvalue:[uicolor darkgraycolor] forkey:nsforegroundcolorattributename];
  // baritemdict的字体
  [baritemtitledict setvalue:[uifont systemfontofsize:13] forkey:nsfontattributename];
  
  // 2.3.用字典 设置barbtnitem的标题文字属性
  [barbtnitem settitletextattributes:baritemtitledict forstate:uicontrolstatenormal];
  [barbtnitem settitletextattributes:baritemtitledict forstate:uicontrolstatehighlighted];
}
@end

dock.h

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//
// dock.h
// 20_帅哥no微博
//
// created by beyond on 14-8-3.
// copyright (c) 2014年 com.beyond. all rights reserved.
// dock就是主控制器下面的一条bar,它里面是由一个个按钮dockbtn组成
 
#import <uikit/uikit.h>
@class dockbtn;
@interface dock : uiview
// 添加一个item到dock(view),参数是图标名,和要显示 的标题 ,以及对应的子控制器的类名
 
- (void)adddockbtnwithiconname:(nsstring *)iconname title:(nsstring *)title viewcontrollerclassname:(nsstring *)viewcontrollerclassname;
 
// 当dock里面的某一个按钮被点击了的时候,调用代码块,处理相应的点击事件
@property (copy,nonatomic) void(^btnclickblock)(dockbtn *);
 
 
 
// 自定义方法,通过代码决定哪一个dockbtn被点击了,参数是 dock栏里面的那个将要被点击的按钮的索引
- (void)setdockbtnclickedatindex:(int)index;
@end

dock.m

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//
// dock.m
// 20_帅哥no微博
//
// created by beyond on 14-8-3.
// copyright (c) 2014年 com.beyond. all rights reserved.
// 这个就是主控制器下面那一栏,tabbar,也叫dock,里面有五个按钮,分别是首页,我,消息,广场,更多
 
#import "dock.h"
#import "dockbtn.h"
 
@interface dock()
{
 
  // 当前选中了那个dockbtn
  dockbtn *_currentdockbtn;
}
@end
 
@implementation dock
// init方法内部会调用initwithframne
- (id)initwithframe:(cgrect)frame
{
  self = [super initwithframe:frame];
  if (self) {
    // 固有固定属性,设置dock背景颜色(分类方法,使用imagename就可进行平铺)
    self.backgroundcolor = [uicolor colorwithpatternimagenamed:@"tabbar_background.png"];
  }
  return self;
}
 
// 给外部提供一个接口,添加一个dockbtn(按钮)到dock(view),参数是图标名,和要显示 的标题,以及对应的子控制器的类名
- (void)adddockbtnwithiconname:(nsstring *)iconname title:(nsstring *)title viewcontrollerclassname:(nsstring *)viewcontrollerclassname
{
  // 1.创建dock里面的按钮,并添加到dock里面
  dockbtn *dockbtn = [dockbtn buttonwithtype:uibuttontypecustom];
  [self addsubview:dockbtn];
  
  // 2.设置dockbtn正常状态下显示 的文字
  [dockbtn settitle:title forstate:uicontrolstatenormal];
  
  // 3.分类方法,设置按钮正常和选中状态下的图片,返回图片尺寸
  [dockbtn setbtnimgfornormalandselectedwithname:iconname];
  
  // 4.设置dockbtn对应点击后,要实例化的子控制器的类名
  [dockbtn setviewcontrollerclassname:viewcontrollerclassname];
  
  // 5.监听点击,只要按下就响应,(事件先传递给dock的方法,dock的方法中再通过调用属性block代码块,从而调用到主控制器里面的代码,原因是:在主控制器里面实例化的dock,在dock里面才实例化的dockbtn,因此,主控制器并不知道dockitem的存在)
  [dockbtn addtarget:self action:@selector(dockbtnclick:) forcontrolevents:uicontroleventtouchdown];
  
  // 6.遍历设置dock里面所有按钮的frame (使之平均分布)
  [self setdockbtnframes];
}
 
// 遍历设置dock里面所有按钮的frame (使之平均分布)
- (void)setdockbtnframes
{
  // 1,获取dock里面所有的按钮个数
  int dockbtnnum = self.subviews.count;
  
  // 2,根据dock中,当前当前有多少个dockbtn,计算出每个dockbtn的宽度(self是dock,320*44)
  cgfloat dockbtnwidth = self.frame.size.width / dockbtnnum;
  cgfloat dockbtnheight = self.frame.size.height;
  
  for (int i = 0; i < dockbtnnum; i++) {
    // 1.逐个取出子控件
    dockbtn *btn = self.subviews[i];
    
    // 2.根据索引 计算它的x
    btn.frame = cgrectmake(i * dockbtnwidth, 0, dockbtnwidth, dockbtnheight);
    
    // 3.初始化的时候,将第0个btn(即首页)选中
    if (i == 0) {
      btn.selected = yes;
      // 最重要的是,将选中的,置为当前的按钮,用成员变量记住,当点击dock上button的时候,先将current置为未选中,然后就被点击的按钮选中,最后最重要的是,将被点击的按钮重新置为当前 的按钮,用成员变量记住
      _currentdockbtn = btn;
    }
    
    // 4.因为点击dock里面的按钮的时候,要知道点击了哪一个按钮,所以给每个按钮绑定一个tag,作为它的索引
    btn.tag = i;
  }
}
 
// 最重要的是,当点击dock上button的时候,先将current置为未选中,然后就被点击的按钮选中,最后最重要的是,将被点击的按钮重新置为当前 的按钮,用成员变量记住
- (void)dockbtnclick:(dockbtn *)btn
{
  // 1.让当前的btn取消选中
  _currentdockbtn.selected = no;
  
  // 2.让新的btn选中
  btn.selected = yes;
  
  // 3.最后,让新的btn变为当前选中btn
  _currentdockbtn = btn;
  
  // 4.调用block,即主控制中传递过来的代码块,目的是处理点击之后的实例化对应的子控制器
  if (_btnclickblock) {
    // 将参数 dockbtn传递过去,给主控制器,它里面成员变量记住了它对应的控制器的类名
    _btnclickblock(btn);
  }
}
 
 
 
// 自定义方法,通过代码决定哪一个dockbtn被点击了,参数是 dock栏里面的那个将要被点击的按钮的索引
- (void)setdockbtnclickedatindex:(int)index
{
  // 1.robust判断
  if (index < 0 || index >= self.subviews.count) return;
  
  // 2.通过索引 拿到对应的dockbtn viewwithtag也行
  dockbtn *btn = self.subviews[index];
  
  // 3.手动调用下面方法,相当于用户用手点击了dock里面对应的按钮
  [self dockbtnclick:btn];
}
@end

dockbtn.h

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//
// dockbtn.h
// 20_帅哥no微博
//
// created by beyond on 14-8-4.
// copyright (c) 2014年 com.beyond. all rights reserved.
// 一个dockbtn代表dock上面的一个按钮,它有个成员是对应子控制器的类名,比如home按钮,成员属性的值就是叫:homeviewcontroller
 
#import <uikit/uikit.h>
 
@interface dockbtn : uibutton
// 每个dockbtn中,用一个成员记住 它对应的控制器的类名
@property (nonatomic,copy) nsstring *viewcontrollerclassname;
@end

dockbtn.m

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//
// dockbtn.m
// 20_帅哥no微博
//
// created by beyond on 14-8-4.
// copyright (c) 2014年 com.beyond. all rights reserved.
// 一个dockbtn代表dock上面的一个按钮,它有个成员是对应子控制器的类名,比如home按钮,成员属性的值就是叫:homeviewcontroller
 
#import "dockbtn.h"
 
// 按钮的内容的总宽度
#define kbtncontentwidth contentrect.size.width
// 按钮的内容的总高度
#define kbtncontentheight contentrect.size.height
 
// 按钮里的图片的所占的高度比例
#define kimageheightratio 0.6
// 按钮里的文本标签的所占的高度比例
#define klabelheightratio (1- kimageheightratio)
 
@implementation dockbtn
 
// 一些默认的通用的属性一定要写在构造方法里面
- (id)initwithframe:(cgrect)frame
{
  self = [super initwithframe:frame];
  if (self) {
    // 1.设置按钮文字属性 (局中,字体大小)
    self.titlelabel.textalignment = nstextalignmentcenter;
    self.titlelabel.font = [uifont systemfontofsize:12];
    
    // 2.设置按钮图片属性 (放大模式,取消按钮默认的点击高亮时的变色)
    self.imageview.contentmode = uiviewcontentmodescaleaspectfit;
    // 取消按钮默认的点击高亮时的变色(image is drawn darker when highlighted or pressed)
    self.adjustsimagewhenhighlighted = no;
    
    // 3.分类方法,设置按钮选中时的背景
    [self setbgimgforselected:@"tabbar_slider.png"];
  }
  return self;
}
 
#pragma mark 重写父类的方法(覆盖父类在高亮时所作的行为)
- (void)sethighlighted:(bool)highlighted
{
  // 因为 这里只需用按钮的选中和默认状态时的图片,所以要取消高亮状态的一些默认变色行为
  // 这里什么也不写,即取消,按钮本身 在高亮的时候执行的那些行为
  
}
 
 
#pragma mark 返回是按钮内部uiimageview的边框(按钮中的图片在上方,居中)
- (cgrect)imagerectforcontentrect:(cgrect)contentrect
{
  // 要居中,最快办法就是让按钮中的图片宽度和按钮一样宽
  return cgrectmake(0, 0, kbtncontentwidth, kbtncontentheight * kimageheightratio);
}
 
#pragma mark 返回是按钮内部uilabel的边框(按钮中的文字在下方,居中)
- (cgrect)titlerectforcontentrect:(cgrect)contentrect
{
  // 要居中,最快办法就是让按钮中的label宽度和按钮一样宽
  
  // 文字的y位于图片的下边线的上方5个单位距离,即距离图片上方5
  cgfloat labely = kbtncontentheight * kimageheightratio - 5;
  // 文字的高度是占按钮余下的所有高度
  cgfloat labelheight = kbtncontentheight - labely;
  return cgrectmake(0, labely, kbtncontentwidth, labelheight);
}
 
@end

模型column.h

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//
// column.h
// 20_帅哥no微博
//
// created by beyond on 14-8-4.
// copyright (c) 2014年 com.beyond. all rights reserved.
// 1个column模型对应dock上面的一个按钮,类别
 
#import <foundation/foundation.h>
 
// 数据模型 代表一个栏目
@interface column : nsobject
 
// 栏目名称
@property (nonatomic,copy)nsstring *columnname;
// 栏目图片名称
@property (nonatomic,copy)nsstring *columnimgname;
// 栏目对应的控制器的类名
@property (nonatomic,copy)nsstring *columnclassname;
// ui控件用weak,字符串用copy,其他对象用strong
 
// 提供一个类方法,即构造函数,返回封装好数据的对象(返回id亦可)
+ (column *)columnnamed:(nsstring *)columnname imgname:(nsstring*)columnimgname classname:(nsstring *)columnclassname;
 
// 类方法,字典 转 对象 类似javabean一次性填充
+ (column *)columnwithdict:(nsdictionary *)dict;
 
// 对象方法,设置对象的属性后,返回对象
- (column *)initwithdict:(nsdictionary *)dict;
 
@end

模型column.m

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//
// column.m
// 20_帅哥no微博
//
// created by beyond on 14-8-4.
// copyright (c) 2014年 com.beyond. all rights reserved.
//
 
#import "column.h"
 
@implementation column
// 返回一个包含了 栏目对应控制器名字的 对象实例
+ (column *)columnnamed:(nsstring *)columnname imgname:(nsstring *)columnimgname classname:(nsstring *)columnclassname
{
  // 为了兼容子类 使用self
  column *column = [[self alloc]init];
  column.columnname = columnname;
  column.columnimgname = columnimgname;
  column.columnclassname = columnclassname;
  return column;
}
 
 
// 类方法,字典 转 对象 类似javabean一次性填充
+ (column *)columnwithdict:(nsdictionary *)dict
{
  // 只是调用对象的initwithdict方法,之所以用self是为了对子类进行兼容
  return [[self alloc]initwithdict:dict];
}
 
// 对象方法,设置对象的属性后,返回对象
- (column *)initwithdict:(nsdictionary *)dict
{
  // 必须先调用父类nsobject的init方法
  if (self = [super init]) {
    // 设置对象自己的属性
    [self setvaluesforkeyswithdictionary:dict];
  }
  // 返回填充好的对象
  return self;
}
 
@end

dock里面的五个栏目按钮的数据来源columnlist.plist

iOS开发之路--微博骨架搭建