qgy 的ios 一些小经验,小技巧



1、 针对UIScrollView 如何做自动布局?



UIScrollView自动布局,这里采用xib的自动布局方式。 这里的小技巧就是给ScrollView 放一个子节点我们给他命名contentView ,它的约束如下图所示,填充整个UIScrollView .  而UIScrollView 的约束也是要填充整个父容器。 , 这样布局有一个好处,化零散为整体。其他的控件约束不容易受影响。
如何让UIScrollView 可以滑动呢?
另外一个注意点就是:在最后一个控件,如上图确认按钮,给确认按钮上下左右的约束。 这样的话就可以滑动了。

但有一个问题出来了,垂直滑动正常,但水平我们是不需要滑动的,问题在于contentView没有给宽度约束。 然而宽度又不能固定给,因为设备的宽度不同,所以通过代码约束的方式给contentView 水平宽度的约束。

//重写UIView 里面的 updateViewConstraints ,第一次加载,系统会自动调用。 如果需要改变约束,可在需要时候调用。
-(void )updateViewConstraints{
   
   
 //设置uiscrollview 里面的contentView 的宽度

   
 NSDictionary *views = NSDictionaryOfVariableBindings(_contentView );
   
 NSString *contentViewWidthStr=[NSString stringWithFormat:@"[_contentView(%f)]",kDeviceWidth];
    [_contentView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:contentViewWidthStr options:0  metrics:nil views:views]];
   [super updateViewConstraints]; //updateViewConstraints 一定要调用。
}






2、 如何通过代码方式来实现自动布局呢?

举个例子: ,让层居于屏幕底部。 
//利用自动布局把按钮放在最底层
   
 NSDictionary *views = NSDictionaryOfVariableBindings(self.view,floatView );
    [
self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[floatView]|" options:0 metrics:0 views
:views]];
    [
self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[floatView(==42)]|" options:0 metrics:0 views
:views]];
   
 floatView.hidden=YES
;
   
   
    [
floatView addSubview
:phoneFollowButton];
    [
floatView addSubview
:visitFollowButton];
   
 NSDictionary *buttonsDictionary = NSDictionaryOfVariableBindings(floatView
,phoneFollowButton,visitFollowButton);
    [
floatView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[phoneFollowButton(==visitFollowButton)][visitFollowButton]|" options:0 metrics:0 views
:buttonsDictionary]];
    [
floatView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[phoneFollowButton]|" options:0 metrics:0 views
:buttonsDictionary]];
    [
floatView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[visitFollowButton]|" options:0 metrics:0 views:buttonsDictionary]];



这里需要注意的问题是,当我们将一个创建好的约束添加到View上时,添加的目标View要遵循以下的规则:

  • 对于两个同层级View之间的约束关系,添加到他们的父View上。
  • 对于两个不同层级View之间的约束关系,添加到他们最近的共同的父View上
  • 对于有层次关系的两个View之间的约束关系,添加到层次较高的父View上


这里重点解释下VFL语法:
1.|-[view]-|:  视图处在父视图的左右边缘内
2.|-[view]  :   视图处在父视图的左边缘
3.|[view]   :   视图和父视图左边对齐
4.-[view]-  :  设置视图的宽度高度
5.|-30.0-[view]-30.0-|:  表示离父视图 左右间距  30
6.[view(200.0)] : 表示视图宽度为 200.0
7.|-[view(view1)]-[view1]-| :表示视图宽度一样,并且在父视图左右边缘内
8. V:|-[view(50.0)] : 视图高度为  50
9: V:|-(==padding)-[imageView]->=0-[button]-(==padding)-| : 表示离父视图的距离
为Padding,这两个视图间距必须大于或等于0并且距离底部父视图为 padding。
10:  [wideView(>=60@700)]  :视图的宽度为至少为60 不能超过  700 ,最大为1000
11: 如果没有声明方向默认为  水平  V:

constraintsWithVisualFormat方法参数解释:
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views;   
 
 
- 它返回一组constraint. 
- format是你的VFL字串,等下讲。
 
- opts自己点去头文件看。有些情况会用,等下有例子。
 
- metrics是一个奇妙的字典,是你自己定义的。这个字典里面的key可以写在format字串中。编译器解析时,自动替换为metrics字典中的value。等下有例子。
 
- views是需要constraint关系的所有view.(也可以是一个)


//通过代码方式自动布局约束(客户详情跟进内容双击全屏显示)
//创建一个UIView 显示在最前端。
-(
void)showDetailFollowInfo:(Follow  *)lookFollow{
   
 showFollowInfoView=[[UIView alloc]init
];
   
 showFollowInfoView.backgroundColor=[UIColor  whiteColor
];
   
 showFollowInfoView.frame=CGRectMake(0, 0, kDeviceWidth, [[UIScreen mainScreen] currentMode].size.height/2
);
    [[
UIApplication sharedApplication].keyWindow addSubview:showFollowInfoView
];
   
   
 UILabel *showFollowDetailLabel=[[UILabel alloc]init];//WithFrame:CGRectMake(20, 0, 280, 21)

    showFollowDetailLabel.
text=lookFollow.content ;
    showFollowDetailLabel.
numberOfLines=0
;
    [showFollowDetailLabel
 sizeToFit];
    showFollowDetailLabel.textColor=[UIColor blackColor];
    showFollowDetailLabel.translatesAutoresizingMaskIntoConstraints=NO//代码方式,要使用自动布局必须加这句话。
    showFollowDetailLabel.font=[UIFont systemFontOfSize:19 ];
    showFollowDetailLabel.
lineBreakMode=NSLineBreakByTruncatingTail;
    [showFollowInfoView  addSubview:showFollowDetailLabel];
    showFollowDetailLabel.preferredMaxLayoutWidth=kDeviceWidth-20//细节之处,使文字多行显示必备项
     showFollowDetailLabel.textAlignment=NSTextAlignmentCenter ;

    //调整位置,让信息显示在屏幕中间
    NSDictionary *views = NSDictionaryOfVariableBindings(showFollowInfoView,showFollowDetailLabel);
    [showFollowInfoView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-10-[showFollowDetailLabel]-10-|" options:0 metrics:0 views:views]];
  
   
 //创建了一个水平居中父视图的约束

   
 NSLayoutConstraint  *constraint = [
                                     
 NSLayoutConstraint

                                     
 constraintWithItem :showFollowDetailLabel
                                     
 attribute:NSLayoutAttributeCenterX

                                     
 relatedBy:NSLayoutRelationEqual
                                     
 toItem:showFollowInfoView
                                     
 attribute:NSLayoutAttributeCenterX
                                     
 multiplier:1.0f
                                     
 constant:00.0f
                                      ];
    //创建了一个垂直居中父视图的约束
    NSLayoutConstraint  *constraint1 = [
                                     
 NSLayoutConstraint

                                     
 constraintWithItem :showFollowDetailLabel
                                     
 attribute:NSLayoutAttributeCenterY

                                     
 relatedBy:NSLayoutRelationEqual
                                     
 toItem:showFollowInfoView
                                     
 attribute:NSLayoutAttributeCenterY
                                     
 multiplier:1.0f
                                     
 constant:00.0f
                                      ];
    [
showFollowInfoView addConstraint :constraint];
    [
showFollowInfoView addConstraint:constraint1];
   
    //点击关闭事件
   
 showFollowInfoView.userInteractionEnabled=YES ;
   
 UITapGestureRecognizer *tapGestrue = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(closeFollowShowView:)];
    [showFollowInfoView addGestureRecognizer:tapGestrue];
}



3、自动布局如何让UILabel 自动换行?

  给内容的label 设置size To Fit  ,然后 设置属性lines为 0 。  另外 timeLabel 一定要给高度约束,因为ios6对约束讲究特别严格,约束需要唯一性等。


4、对UITableView 的一些思考和技巧。

1)针对比较复杂的cell,然后高度又不统一 ,可以采用代码方式创建cell . 举例:话题列表。

  大致思路:准备一个实体模型,一个自定义的cell ,一个计算高度的实体FrameBean .
1、在请求数据成功的时候,把所有实体封装到frameBean中。FrameBean 的用途在于 :根据实体计算好各个控件的frame ,方便在cellFor 显示实体数据的时候直接拿各个控件的frame .
                NSMutableArray *childObjects = [JSonHelper parseToObjectArray:list customClass:[ForumThread class]];
                NSMutableArray *childObjectsFrameArr=[[NSMutableArray alloc]init ];
               
 for (ForumThread *forumThreadUse in 
childObjects) {
                   
 //在返回数据的时候得到对象cellFrame

                   
 ForumThreadFrame *forumThreadFrameNow=[[ForumThreadFrame alloc]init ];
                    forumThreadFrameNow.
forumThread
=forumThreadUse;
                    [childObjectsFrameArr
 addObject:forumThreadFrameNow];
                }
                [self.paginator receivedResults:childObjectsFrameArr total:totalCount];

2、显示cell , 在 cellForRowAtIndexPath 方法中 cell.forumThreadFrame=[self.paginator.results objectAtIndex:indexPath.row];
在自定义的cel 中 重写    -(void)setForumThreadFrame:(ForumThreadFrame *)forumThreadFrame 方法,在方法里面设置值,和设置cell 各个控件的frame.
  
3、设置cell 的高度: 直接从数组中取出 cellHeight。得到该cell的高度。就不用再次计算cell 高度了。
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath  *)indexPath{
   
 return [[self.paginator.results objectAtIndex:indexPath.row] cellHeight];  //获得话题cell的高度
}

2)如何给单元格注册双击事件呢?

由于uitableViewCell 没有双击事件,所以这里可以通过单击的时间间隔判断是否为双击事件。举例:客户详情,售楼员的跟进消息。双击单元格可以全屏显示跟进信息。列如下面的代码。
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath  *)indexPath{
   
   
 follow = [self.paginator.results objectAtIndex:indexPath.row
];
   
 //如果两次点击的时间间隔小于1秒,则断定为双击事件

   
 NSUInteger curr = [[NSDate date] timeIntervalSince1970 ];
   
 if (curr-taptime<1
) {
        [
self showDetailFollowInfo:follow
];
    }
   
 taptime = curr;
}

3)针对自定义cell里面的控件有时候需要触发事件 可以通过委托,调用到viewController 的方法。
 举例:1、话题点赞和回复。则是通过设置委托,调用viewController对应的方法进行点赞和回复处理。
            2、跟进模板,长按移动功能也是通过cell里面的事件委托调用viewController的方法进行移动处理。

4)注意细节。ios6 和ios7 之后,UITableViewCell 里面有个父容器不同。所以有的控件苹果建议添加在uitableView 的contentView 里面。 
注: self 是自定义的uitableViewCell
UITableView *tableView=[self.superview isKindOfClass:[UITableView class]]?(UITableView *)self.superview:(UITableView *)self.superview.superview;

4)表格cell的重用。
方式一:通过代码方式创建自定义的UITableViewCell .
 ForumThreadCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if(cell == nil){
           cell = [[ForumThreadCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
方式二:通过xib方式创建自定义的UITableViewCell .
    static NSString *CellIdentifier = @"commissionerCell";
    CommissionerCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if(cell == nil ){
        cell  = [[
NSBundle mainBundle] loadNibNamed:@"CommissionerCell" owner:nil options:nil][0];
    }

总结:充分利用好重用机制。对于数据量大的话,重用可以大大明显提高运行效率。


5)表格cell的选择:单选与多选。MVC 模式的使用。
     举例1: 单选多选。

实现长按多选同时改变样式的流程分析:
1、长按cell,首先要注册长按事件。 
2、长按开始,这里首先有一个细节就算 ,长按了,也就选中了该对象,该给长按的那个cell加上样式。  简单的说,长按开始 ,也相当于单击了cell。 同时,开启了长按的标志状态。所以用户就可以在点击单元格的时候,根据状态来多选了。
3、多选的对象要存放到数组中去。再次点击单元格的时候就要判断数组中到底是否已有该对象了,如果有,则取消cell的选中样式,删除数组中的cell对应的对象。如果没有,则可以给当前点击的cell加高亮选中的样式,同时往数组中添加对象。
4、再次对cell 长按的时候,就可以根据记录的长按次数,长按的奇数偶数次数分别改变着长按的标记状态,也就算开关,决定着是单选还是多选。
5、长按次数为偶数次的时候,不仅仅要更改状态,同时应该把长按已多选的数组中存放的对象清空, 还应该取消说有已经选择的cell 样式(这个只需要刷新表格就可以)


细节注意:已多选的cell 滑动的时候可能会因为表格的重用而把样式取消了,所以,在表格显示的时候,也要检查多选数组中是否存有该对象,若有,则不把高亮样式取消,反之。 可通过以下类似代码来判断。
  在cellForIndexPath 方法中加入以下代码防止滑动过程中,因表格重用因素而导致表格的样式改变。
  if([multipleDeleteArr indexOfObject:templet.followTemplateId]!= NSNotFound ){
               
 markImageView.image=[UIImage imageNamed:@"selected@2x.png"
];
  }


 举例2:模板的多选删除。
                   
针对多选删除,也是和上面的推荐赚钱 ,选中楼盘类似的原理。

 举例3:mvc模式的应用。

 如果处理最后一个家人左边多余的垂直线不显示。 这里思路是给家人这个实体设置一个属性,记录是第几个实体。然后显示实体的时候,在cellForIndexPath 中就可以判断该实体的位置和总数量比较就可以控制最后一个实体的显示样式。
          [self.paginator receivedResults:childObjects total:totalFamilyCount]; //nowPage*total+1
            //给所有实体标记位置
           
 int positionTag=1 ;
           
 for (Team *team in  self.paginator.results
) {
                team.
positionTag=[NSString stringWithFormat:@"%d"
,positionTag];
                positionTag++;
            }
            [self.dataTableView reloadData];



5)表格 UISearchDisplayController+UISearchBar 搜索功能阐述
     举例:在的城市列表中所用到了城市搜索。 这里要注意几点事项。
     1、 iOS7以上在搜索情况下看不到导航栏右侧的取消按钮。 所以可以通过自定义设置UIBarButtonItem的样式。
  if(iOS7){
        [[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nilsetTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:                    [UIColor colorWithRed:116/255.0 green:116/255.0 blue:116/255.0 alpha:1],UITextAttributeTextColor,[NSValue valueWithUIOffset:UIOffsetMake(00)],UITextAttributeTextShadowOffset,nilforState:UIControlStateNormal];
   }

  2、通过苹果自带的系统方法 将汉字转为不带音调的拼音。
+(NSString *)transformMandarinToLatin:(NSString *)string
{
   
 NSMutableString *preString = [string mutableCopy
];
   
   
 /*转换成成带音 调的拼音*/

   
 if( CFStringTransform((CFMutableStringRef)preString, NULL, kCFStringTransformMandarinLatin, NO )){}
   
 /*去掉音调*/

   
 if(CFStringTransform((CFMutableStringRef)preString, NULL, kCFStringTransformStripDiacritics, NO )){}
   
    return preString;
}

     3、通过以下三个方法来进行搜索过滤,过滤后,系统会自动刷新表格,让筛选的数据显示在系统自带的查询结果集的表格上

 - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString 
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption
-(void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope  




5、 svn 不能提交.a的解决办法

svn不能提交.a文件, 这个主要与SVN的配置有关. 解决方法如下:

  1. 打开终端, 在命令行中输入: open ~/.subversion/config 来打开配置文件.
  2. 然后, 在文件中搜索 [auto-props] ,并在它上面添加: 

    global-ignores=.o .lo .la .al .libs .so .so.[0-9] .pyc .pyo .rej ~ ## .# ..swp .DS_Store .xcuserstate 



    3.保存,刷新svn,你会发现svn 对 .a 文件的 标识 不再是 i,而是 ? ,这说明 .a 文件 已经在svn的控制中了,然后 add 。commit

这里的意思是, SVN在提交时自动忽略以这些后缀的文件, 那么我们要去掉.a这一项, 则将配置文件改为 

global-ignores = 
.o .lo .la .al .libs .so .so.[0-9] .pyc .pyo .rej ~ ## .# .*.swp .DS_Store 

保存退出. 就可以了.

你可以根据自己的需要修改其他的后缀名. 


6、 关于IOS8之后 选项卡设置背景图片变蓝色解决办法。
    UIImage *selectedHomeImage=[UIImage imageNamed:@"nav_home_hl"];
    //申明这张图用原图,不用经过系统渲染
   
 if(iOS7){
        selectedHomeImage = [selectedHomeImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    }
    //设置选项卡图片
    [homeViewController.tabBarItem setFinishedSelectedImage:selectedHomeImage withFinishedUnselectedImage:[UIImage imageNamed:@"nav_home"]];

7、长按图片保存到本地相册
 
#pragma -mark ActionSheet 委托 的点击按钮事件

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger )buttonIndex{
   
 if(buttonIndex==0){ //保存图片到本地相册

       
 if(needSaveImage!=nil ){
           
 UIImageWriteToSavedPhotosAlbum(needSaveImage, self,@selector(image:didFinishSavingWithError:contextInfo:),nil
);
        }
    }
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void  *)contextInfo;
{
   
 // Handle the end of the image write process

   
 if  (!error)
       [
self initHudWithCaptionOnly:@"已保存到本地相册"
];
   
 else

       [
self initHudWithCaptionOnly:[NSString stringWithFormat:@"保存错误: %@",error]];
}


8、如何在图片上绘制红点 ?
-(UIImage*)imageAddRedDot:(NSString*)imageName
{
    UIImage* originalImage = [UIImage imageNamed:imageName];
   
    CGRect rect = CGRectMake(
0, 0, 29, 29
);
    UIGraphicsBeginImageContextWithOptions(rect.size,
 NO, 0.0
);
    CGContextRef context = UIGraphicsGetCurrentContext();
   
    [originalImage drawInRect:CGRectMake(
0, 0, 29, 29)]; //图片大小

   
   
    CGRect borderRect = CGRectMake(
21.0, 0.0, 8.0, 8.0); //点大小
   
    CGContextSetRGBStrokeColor(context,
 1.0, 1.0, 1.0, 1.0 );
   
    CGContextSetFillColorWithColor(context,[UIColor redColor].CGColor);
    CGContextSetLineWidth(context,
 1.0
);
    CGContextFillEllipseInRect (context, borderRect);
    CGContextStrokeEllipseInRect(context, borderRect);
    CGContextFillPath(context);
   
    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
   
    UIGraphicsEndImageContext();
   
   
 return 
theImage;
}



9、UILabel 通用文字换行:
     
           之前一直纠结这文字换行,因为之前可能对一个属性没有接触过,对换行一直怀恨在心。 因为之前在做应用的时候,有文字换行成功了,但是行数较多情况下,换行后多行显示的文字没有在框的顶部,而是居中,结果把label 的高度撑高了。照成label 所显示出来的效果偏移,或者出现很难看的效果。以下代码解决了这个bug .

//文字换行,宽度固定,高度自适应。参数只需传:label, 内容,显示行数 (多行用0表示) 此方法写在BaseViewController中 了
- (void) heightForString:(UILabel *)label andContext:(NSString *)contextStr withLineNum:(int ) publicLineNum{
    label.
numberOfLines
=publicLineNum;
    label.
text
=contextStr;
   
 CGRect rect =label.frame
;
   
 CGSize sizeToFit = [label sizeThatFits:CGSizeMake(rect.size.width, MAXFLOAT
)];
    rect.
size.height = sizeToFit.height
;
    label.
frame 
= rect;
    label.
lineBreakMode = UILineBreakModeTailTruncation; //UILineBreakModeTailTruncation  , UILineBreakModeWordWrap 截去尾部

}


10、文字换行 ios7 后使用 NSMutableAttributedString ,此属性可以加下划线,之类的,等等
-(void)changeLine:(UILabel *)dataInfoLable  andContext:(NSString *)dataText withLineNum:(int) publicLineNum{
    dataInfoLable.numberOfLines=publicLineNum;
   
 NSMutableAttributedString * attributedString = [[NSMutableAttributedString alloc] initWithString:dataText];
    NSMutableParagraphStyle * paragraphStyle = [[NSMutableParagraphStyle alloc] init ];
    [attributedString 
addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0dataText.length)];
    [dataInfoLable setAttributedText :attributedString];
   
 [dataInfoLable sizeToFit]; //长宽都自动适应。

    dataInfoLable.
lineBreakMode = UILineBreakModeTailTruncation; //UILineBreakModeTailTruncation, UILineBreakModeWordWrap 截去尾部   
}


11、给label加下划线
-(void)addUnderLineForLabel:(UILabel *)label  andContext:(NSString *)contextStr{

   
 NSMutableAttributedString *content = [[NSMutableAttributedString alloc]initWithString:contextStr];
    NSRange contentRange = {0,[content length ]};
    [content
 addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range
:contentRange];
    
label.attributedText = content;

}



 12、设置UILable展示的行间距
        NSString *lightValue=[[groupRowsArr objectAtIndex:section] objectAtIndex :rowIndex];
        
NSMutableAttributedString * attributedString1 = [[NSMutableAttributedString allocinitWithString
:lightValue];
        
NSMutableParagraphStyle * paragraphStyle1 = [[NSMutableParagraphStyle allocinit
];
        [paragraphStyle1 
setLineSpacing:2
];
        [attributedString1 
addAttribute:NSParagraphStyleAttributeName value:paragraphStyle1 range:NSMakeRange(0, lightValue.length
)];
        [targetLable 
setAttributedText
:attributedString1];
        [targetLable 
sizeToFit];





13、UIActionSheet 系统弹出层:(记得要实现UIActionSheetDelegate协议),轻松解决弹出选择的效果。

#pragma -mark  点击打电话
- (void)callPhone:(UITapGestureRecognizer  *)gesture
{
   
   
 UIActionSheet *actionSheet = [[UIActionSheet alloc
]
                                 
 initWithTitle:[NSString stringWithFormat:@"联系客户:%@",_customer.customerName
]
                                 
 delegate:self

                                 
 cancelButtonTitle:@"取消"
                                 
 destructiveButtonTitle:_customer.customerTel
                                 
 otherButtonTitles:@"复制",nil ];
    actionSheet.
actionSheetStyle = UIActionSheetStyleDefault
;
    [actionSheet
 showInView:self.view
];
}


#pragma -mark ActionSheet 委托 的点击按钮事件

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger )buttonIndex{
   
 if(buttonIndex==0
){
        [[
UIApplication sharedApplication] openURL:[NSURL URLWithString:[ NSString stringWithFormat:@"tel://%@",_customer.customerTel
]]];
    }
else  if(buttonIndex==1
){
       
       
 UIPasteboard *pasteboard = [UIPasteboard generalPasteboard
];
       
 NSString *pastContext=[NSString stringWithFormat:@"%@ %@",_customer.customerName,_customer.customerTel
];
        pasteboard.
string 
= pastContext;
    }
}






14、切换登录的时候进行刷新tabBarController 的  viewControllers (第几个)UINavigationController  里面的 navCtrl.viewControllers  的第一个。(在切换登录或者切换城市的情况下可能要刷新tabBarController里面的ViewController)

if([url isEqualToString:mineRoleUrl ]){
          
NSDictionary *dic=(NSDictionary
 *)extendData;
          
NSString *rolesType=[NSString stringWithFormat:@"%@",[dic objectForKey:@"rolesType"
]];
           
// 进行保存权限

          [[
NSUserDefaults standardUserDefaultssetObject:rolesType forKey:AGENT_ROLE ];
          [[
NSUserDefaults standardUserDefaultssynchronize
];
          [
self close:nil
];
        
        
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplicationdelegate
];
        
if([rolesType isEqualToString:@"1"]||[rolesType isEqualToString:@"0"]){ //专员

            
                
//选中第1个点tab
                [appDelegate.
tabBarController setSelectedIndex:0 ];
        }
if([rolesType isEqualToString:@"2"]){ //售楼员

            
                
//选中第2tab
                [appDelegate.
tabBarController setSelectedIndex:1 ];
        }
if([rolesType isEqualToString:@"3"]){ //专员、售楼员

            
                
//选中第1tab
                [appDelegate.
tabBarController setSelectedIndex:0 ];
        }
        
        [
self reloadSalesPerson
:appDelegate];
        [
self reloadCommissioner
:appDelegate];
        [
self reloadMessageList
:appDelegate];
        
    }


//刷新选项卡的售楼员模块 (该方法是一个刷新售楼员模块的列子)
-(void)reloadSalesPerson:(AppDelegate  *)appDelegate{
    
NSString *isClickedSecond=[[NSUserDefaults standardUserDefaultsobjectForKey:@"HaveClickSecond"
];
    
if([isClickedSecond isEqualToString:@"1"
]){
        
//判断售楼员模块是否点击过,如果点击过了则刷新售楼员模块

        
UIViewController  *navController=[appDelegate.tabBarController.viewControllers objectAtIndex:1 ];
        
if([navController isKindOfClass:[UINavigationController class
]])
        {
            
UINavigationController *navCtrl = (UINavigationController
 *)navController;
            
CustomerListViewController  *customerListViewController=(CustomerListViewController*)[navCtrl.viewControllers objectAtIndex:0
];
            
if(customerListViewController!=nil
){
                [customerListViewController 
reloadCustomerList
];
            }
        }
    }

}



 
15、json 字符串转换成字典
  NSData *data = [ notification.operateObject dataUsingEncoding:NSUTF8StringEncoding ];
    
NSDictionary *operateDic = [data objectFromJSONData];//JSONKit中的解析函数 objectFromJSONData


16、get方式传中文时候进行汉字转码

 //进行汉字转码
 keyWord=[StringUtil urlEncode:keyWord];

//这个方法可以写到公共类中作为工具类,此代码写在了StringUtil类中了
+(NSString*)urlEncode:(id<NSObject >)value
{
    
//make sure param is a string

    
if ([value isKindOfClass:[NSNumber class ]]) {
        value = [(
NSNumber*)value stringValue
];
    }
    
    
NSAssert([value isKindOfClass:[NSString class]], @"request parameters can be only of NSString or NSNumber classes. '%@' is of class %@.", value, [value class
]);
    
    
return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes
(
                                                                                 
NULL
,
                                                                                 (
__bridge CFStringRef
) value,
                                                                                 
NULL
,
                                                                                 (
CFStringRef)@"!*'();:@&=+$,/?%#[]",                                                                              kCFStringEncodingUTF8
));
}




17、ASIHttpRequest 请求时候,如果要传数组则写了以下方法
#warning  ajax for NSDictionary  如果dic 存放了数组,那么设置 withArray  YES
-(void) ajax:(NSString *)url params:(NSDictionary *)dic withArray:(BOOL)isWithArray  dialog:(BOOL
)isShowDialog{

   
 if
(isShowDialog){
        [
self initHudWithCaptionAndActivity
];
    }
   
   
 if([url isEqualToString:URL_LOGIN
]){
        [[
NSUserDefaults standardUserDefaults] removeObjectForKey:COOKIE
];
    }
   
   
   
 formDataRequest = [ASIFormDataRequest requestWithURL:[NSURL URLWithString
:url]];
    [
formDataRequest setRequestMethod:@"POST"
];
    [
formDataRequest setUseCookiePersistence:NO
];
   
 formDataRequest.delegate = self
;

   
 NSString *cookies = [[NSUserDefaults standardUserDefaults] objectForKey:COOKIE
];

   
 if
(cookies){
        [
formDataRequest addRequestHeader:@"Cookie" value
:cookies];
    }
   
   
 NSEnumerator *emkey = [dic keyEnumerator
];
   
 for (NSObject *key in 
emkey) {
       
       
 id arr=[dic objectForKey
:key];
       
 if([arr isKindOfClass:[NSArray class]]||[arr isKindOfClass:[NSMutableArray class
]]){
           
 NSArray 
*sendArr=arr;
           
 for (id obj in
  sendArr) {
                  [
formDataRequest addPostValue:obj forKey:[key description]]; //向同一个key 添加值,就是成数组了。

            }
        }
else {
            [
formDataRequest addPostValue:[dic objectForKey:key] forKey:[key description
]];
        }
    }

    [
formDataRequest setTimeOutSeconds:20
];
    [
formDataRequest startAsynchronous
];
}



  18、 ios 设置按钮图片大小
//显示更多的按钮上的图片变小
-(
UIImage*)scaleToSize:(UIImage*)img size:(CGSize )size
{
    
// 创建一个bitmapcontext

    
// 并把它设置成为当前正在使用的context
    
UIGraphicsBeginImageContext (size);
    
    
// 绘制改变大小的图片

    [img 
drawInRect:CGRectMake(00, size.width, size.height )];
    
    
// 从当前context中创建一个改变大小后的图片

    
UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext ();
    
    
// 使当前的context出堆栈

    
UIGraphicsEndImageContext ();
    
    
// 返回新的改变大小后的图片

    
return  scaledImage;
}



//
按钮上显示文字和图片 :左边文字,右边图片
 [showMoreButton setTitleEdgeInsets:UIEdgeInsetsMake(0, -imgArrow.size.width0, imgArrow.size.width )];
                [showMoreButton 
setImageEdgeInsets:UIEdgeInsetsMake(0, showMoreButton.titleLabel.bounds.size.width0, -showMoreButton.titleLabel.bounds.size.width
)];
                [showMoreButton 
addTarget:self action:@selector(showMore:) forControlEvents:UIControlEventTouchUpInside];




 19、ios 获取网络 wifi ,2G,3G 。无网络。(此代码写在BaseViewController了)
typedef enum  {
    NETWORK_TYPE_NONE= 
0
,
    NETWORK_TYPE_WIFI= 
1
,
    NETWORK_TYPE_3G= 
2
,
    NETWORK_TYPE_2G= 
3
,
}NETWORK_TYPE;

//然后通过获取手机信号栏上面的网络类型的标志

+ (
int )dataNetworkTypeFromStatusBar {
    
    
UIApplication *app = [UIApplication sharedApplication
];
    
NSArray *subviews = [[[app valueForKey:@"statusBar"valueForKey:@"foregroundView"subviews
];
    
NSNumber *dataNetworkItemView = nil
;
    
    
for (id subview in
 subviews) {
        
if([subview isKindOfClass:[NSClassFromString(@"UIStatusBarDataNetworkItemView"class
]]) {
            dataNetworkItemView = subview;
            
break
;
        }
    }
    
    
int netType = NETWORK_TYPE_NONE
;
    
NSNumber * num = [dataNetworkItemView valueForKey:@"dataNetworkType"
];
    
if (num == nil
) {
        
        netType = 
NETWORK_TYPE_NONE
;
        
    }
else
{
        
        
int n = [num intValue
];
        
if (n == 0
) {
            netType = 
NETWORK_TYPE_NONE
;
        }
else if (n == 1
){
            netType = 
NETWORK_TYPE_2G
;
        }
else if (n == 2
){
            netType = 
NETWORK_TYPE_3G
;
        }
else
{
            netType = 
NETWORK_TYPE_WIFI
;
        }
        
    }
    
    
return netType;
}


20、在ios开发中,我所遇见过的一些常见错误记录。

1,  错误信息:
  "_OBJC_CLASS_$  xxxxx  ", referenced from:
      objc-class-ref in ViewController.o
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)
解决方法:
 查看工程,看是不是没有导入相关的框架。或者工程里添加的有相同".m",".h" 文件

2,  错误信息:
Couldn't register dy.CKRiLiText with the bootstrap server. Error: unknown error code.
This generally means that another instance of this process was already running or is hung in the debugger.Current language:  auto; currently objective-c
解决方法: 可能是电脑内存问题引起,重启电脑即可解决。如果重启解决不了问题,那就是你刚刚改动的代码引起的问题。

3 、 错误信息:
ios 5是调试正常的,ios 6真机调试的时候,出现如下错误:ld: file is universal (3 slices) but does not contain a(n) armv7s slice: /Users/mac4/Desktop/my desktop/My app/MyApp name 20:09:12  /MyApp name/ZBarSDK/libzbar.a for architecture armv7serror: linker command failed with exit code 1 (use -v to see invocation)
解决方法:在Xcode里,点击相应的Target,然后点Build Settings,找到VALID_ARCHS,看里面的是不是arvm7s,如果不是改成arvm7s就可以了。

4 、 错误信息:
 error: receiver type 'ViewController' for instance message does not declare a method with selector 'hideSearchBar:' [4]
ViewController 中没有声明一个方法选择'hideSearchBar:
解决方法:
在ViewController .h 中声明一下这个方法 “ hideSearchBar ”  即可。

5、 错误信息:当json从服务端请求时得到的字符串,如果这样写的话,会报错,';' after top level declarator
NSString *ss= @"{"recommend":"世界末日","dogname":"机器人"}"; 
解决方法:
就是,把   “   替换成  \"  即可。NSString *ss= @"{ \"recommend \": \"世界末日 \", \"dogname \": \"机器人 \"}"; 

6 、 错误信息:
 error: Existing instance variable '_datasource' for property 'datasource' with  assign attribute must be __unsafe_unretained
解决方法:
     id _datasource; 改为     __unsafe_unretained id _datasource:即可

7 、 错误信息:
error: No architectures to compile for (ONLY_ACTIVE_ARCH=YES, active arch=x86_64, VALID_ARCHS=i386).
解决方法:
     targets ->build setting 下的 Build Active Architecture Only 设置 NO  即可. 





21、对新房相册的几点思考和总结。


需求:
     1、总共有5个相册,每个相册滑动到最后一张的时候,就可以自然地滑动到下一个相册,到下一个相册的第一个点第一张图片。如果向左滑动的饿时候,滑到每个相册的第一张图片的时候,继续向左边滑动,则可以滑动到上一个相册。
     2、当滑动到最后一个相册的最后一张图片的时候,继续向右滑动则调到第一个相册的第一张图片。
     3、在滑动过程中如果遇到了空的相册,则跳过。
     4、可以对每张图片进行缩放。


思路分析:
     要达到这种效果,我目前想到两种处理思路。
     第一种:每一次切换相册,通过传递一个数组,来显示新的相册的图片。遇到空,则跳过,取下一个数组的相片。
     第二种:整个过程就一个数组的相片在滑动,第几个相册的第一张图片的滑动位置要确定,这样才能正确显示第几张图片。


第二种思路的过程:
     1、滚动原理: 根据多少张图片来确定ScrollView 的Contentsize: [carScroll setContentSize:CGSizeMake(320 * adCount,imageHeight)]; //adCount是所有相册总共的图片数量。(包括空相册算一张图片)
每滑动一次scrollView就滚动到一定的距离:   [carScroll scrollRectToVisible:CGRectMake(320*pageControl.currentPage, 0, 320, imageHeight) animated:YES]; 
    2、在计算前面有多少张相片时候,如果某个相册为空,那么也要记得加上这张空的照片的数量。
    3、去到下一个相册或上一个相册 的时候,是改变scrollView 显示滑动的位置,从而显示某个相册的某个图片。不是传递数组。而下面的按钮,只需要改变对应的背景和下划线即可。
    4、遇到空相册的时候或者最后一个相册或第一个相册的时候,是重新加载显示UIscrollView .从而移动到某个位置。这样就防止是闪动的情况。
    5、缩放就是要返回在缩放的图片即可,这个只需知道当前缩放的UIScrollView 里面的UIImageView 就可以了。
    6、缩放后滑动到下一张的时候,记得把上次缩放的图片设置到原来的位置,原来的大小。


/*  思路:
 
 NSArray allPictures=[[],[],[],[] ];
 
 
点击某个相册:  index
 
图片滑到了第几张呢? 如果点击的相册不是第一个相册。  allPictures[index-1].count +1  否则: 1 张。

 
 
从外面点击图片进来选中图片:
 
查找到图片在哪个相册index, 第几张page,如果找到的图片不在第一个相册  allPictures[index-1].count + page 否则:  page
 
 如果滑到了最后一个相册的最后一张图片: 此时hu
 */


委托说明:

@protocol PhotosScrollViewDelegate <NSObject ]] >

//单击了哪张图片

- (
void)clickWhichImageView:(NSInteger )curImgPage;

//返回状态,判断是上一个相册,还是下一个相册

- (
void)changeAlbum:(NSInteger )status;

//是否点击切换的

-(
BOOL )isClickChange;//用户更换相册的时候,确定到底是点击到另外的相册还是滑动到另外的相册,因为如果是点击的,如果为空也可以直接显示。

//是否遇到空相册

-(
BOOL)isSlideMeetEmptyPicture;  //在滑动上下相册的时候,检测是否遇到了空相册
-(
void)resetIsSlideMeetEmptyPicture; //重置遇到空相册的状态

@end

//拖动结束,判断是去上一个相册,还是下一个相册。
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
//开始拖动
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
//滚动停止时,触发该函数
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
// 触摸屏幕来滚动画面还是其他的方法使得画面滚动,皆触发该函数
-(void) scrollViewDidScroll:(UIScrollView *)scrollView



22、UIWebView 内存暴增挂掉的处理办法。
-(void)webViewDidFinishLoad:(UIWebView *)webView{

  // new for memory cleaning  因为 每次打开一个链接就会把WebKitCacheModelPreferenceKey 设置成1
    [[
NSUserDefaults standardUserDefaults] setInteger:0 forKey:@"WebKitCacheModelPreferenceKey" ];
    [[
NSUserDefaults standardUserDefaults] setBool:NO forKey:@"WebKitDiskImageCacheEnabled"
];
    [[
NSUserDefaults standardUserDefaults] setBool:NO forKey:@"WebKitOfflineWebApplicationCacheEnabled"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

23、如何用NSUserDefault存储对象?(也有另外一个思路。直接用NSUserDefault 存储json字符串,解析的时候直接用json工具进行解析成对象。具体哪种效率高还不太清楚。)
//在viewController加载的是时候进行 对象解码
    NewHouseDetail *NHD=[[NewHouseDetail alloc]init ];
   
 NSData *newHouseDetailObject = [NSKeyedArchiver archivedDataWithRootObject
:NHD];
   
 localLastTuanGouDetailKey=[NSString stringWithFormat:@"lastTuanGouDetail_%@",_tgID
];
    newHouseDetailObject=[[
NSUserDefaults standardUserDefaults] objectForKey:localLastTuanGouDetailKey]; //上一次的对象

    NHD= [
NSKeyedUnarchiver unarchiveObjectWithData :newHouseDetailObject] ;
   
 newHouse
=NHD;
   
 if(newHouse!=nil){ //本地有缓存的时候,直接从缓存中显示出来

        [
self reloadNewHouseDetialData];
    }


//在请求成功的时候,在success 方法中进行对象编码,本地化存储。 当然第一次加载的时候,也应该显示加载的内容。
      NSData *newHouseDetailObject = [NSKeyedArchiver archivedDataWithRootObject:tuanGouDetail];
      [[NSUserDefaults standardUserDefaults setObject:newHouseDetailObject forKey:localLastTuanGouDetailKey];
      [[NSUserDefaults standardUserDefaults] synchronize];

前提注意:NSUserDefault存储对象的前提是对象要实现 NSCoding 协议。同时在实体中实现对每个属性进行解码和编码。

下面的两个方法是通过反射的方式取得本类的所有属性。进行设置值。这种方法比较简单,代码量少。
注意要导入 #import "NSObject+reflect.h” 。propertyNames方法具体在该NSObject+reflect.h 类中。
//进行解码
- (
id) initWithCoder: (NSCoder  *)coder
{
   
 if (self = [super init])
    {
        for (NSString *key in [self propertyNames:[self class ]])
        {
           
 // Decode the property, and use the KVC setValueForKey: method to set it

           
 id value = [coder decodeObjectForKey :key];
            [
self setValue:value forKey:key];
        }
    }
    return self;   
}


//进行编码
- (
void) encodeWithCoder: (NSCoder  *)coder
{
   
 // Loop through the properties

   
 for (NSString *key in [self propertyNames:[self class ]])
    {
       
 // Use the KVC valueForKey: method to get the property and then encode it

       
 id value = [self valueForKey :key];
        [coder
 encodeObject:value forKey
:key];
    }
}
    

#import "NSObject+reflect.h”  反射工具类。 使得NSObject 可扩展一个方法。



 

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值