本文僅記錄日常iOS開(kāi)發(fā)的一些技巧, 不定時(shí)更新, 最新的置頂, 歡迎關(guān)注打賞訂閱
5.記錄百度地圖的一個(gè)bug:
百度地圖刷新頁(yè)面標(biāo)注點(diǎn)的時(shí)候, 首先需要?jiǎng)h除所有標(biāo)注,如下
//刪除所有標(biāo)注
NSArray *arrayAnmation=[[NSArray alloc] initWithArray:_mapView.annotations];
[_mapView removeAnnotations:arrayAnmation];
然后添加新的標(biāo)注:
[_mapView addAnnotation:point];
添加完標(biāo)注之后, 就會(huì)響應(yīng)下面的回調(diào):
- (BMKAnnotationView *)mapView:(BMKMapView *)mapView viewForAnnotation:(id<BMKAnnotation>)annotation{
BMKAnnotationView* view = nil;
view = [mapview dequeueReusableAnnotationViewWithIdentifier:@"start_node"];
}
問(wèn)題就出在上面的view, 調(diào)用復(fù)用之后, 心想已經(jīng)刪除所有標(biāo)注了, 調(diào)用[mapview dequeueReusableAnnotationViewWithIdentifier:@"start_node"];后應(yīng)該返回nil, 但在添加第一個(gè)標(biāo)注點(diǎn)的時(shí)候, 發(fā)現(xiàn)會(huì)有返回值, 而不是空. 除了第一個(gè)標(biāo)注點(diǎn)之外, 卻不會(huì)有返回值, 于是加入下面的代碼:
- (BMKAnnotationView *)mapView:(BMKMapView *)mapView viewForAnnotation:(id<BMKAnnotation>)annotation{
BMKAnnotationView* view = nil;
view = [mapview dequeueReusableAnnotationViewWithIdentifier:@"start_node"];
if (view == nil) {
view = [[BMKAnnotationView alloc]initWithAnnotation:routeAnnotation reuseIdentifier:@"start_node"];
}else{
view = nil;
view = [[BMKAnnotationView alloc]initWithAnnotation:routeAnnotation reuseIdentifier:@"start_node"];
}
return view;
}
總結(jié): 刪除標(biāo)注點(diǎn)后, 內(nèi)存還會(huì)有一份復(fù)用的view保存著, UITableView的復(fù)用機(jī)制應(yīng)該也是這樣的. 所以即使刪除了所有復(fù)用視圖, 還是要判空一下.
4.有些場(chǎng)景下, 我們使用Masonry進(jìn)行自動(dòng)布局時(shí), 會(huì)獲取不到frame或者bounds
例如當(dāng)想設(shè)置圖片只有兩個(gè)圓角的時(shí)候, 或者設(shè)置控件需要取得frame的時(shí)候, 會(huì)發(fā)現(xiàn)frame或者bounds都為0, 即這樣設(shè)置圓角時(shí):
_thumbView = [[UIImageView alloc] init];
[self.contentView addSubview:_thumbView];
[_thumbView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_bubbleView.mas_left);
make.size.mas_equalTo(CGSizeMake(kGridViewHeight, kGridViewHeight ));
make.top.equalTo(_bubbleView.mas_top);
}];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:_thumbView.bounds byRoundingCorners:UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii:CGSizeMake(6, 6)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = _thumbView.bounds;
maskLayer.path = maskPath.CGPath;
_thumbView.layer.mask = maskLayer;
圖片直接就不顯示了, 打個(gè)斷點(diǎn)在maskLayer.frame = _thumbView.bounds;的前面, 會(huì)發(fā)現(xiàn)_thumbView.bounds全部為0.于是搜尋各種方法, 了解到了下面這些方法:
setNeedsLayout:告知頁(yè)面需要更新,但是不會(huì)立刻開(kāi)始更新。執(zhí)行后會(huì)立刻調(diào)用layoutSubviews。
layoutIfNeeded:告知頁(yè)面布局立刻更新。所以一般都會(huì)和setNeedsLayout一起使用。如果希望立刻生成新的frame需要調(diào)用此方法,利用這點(diǎn)一般布局動(dòng)畫(huà)可以在更新布局后直接使用這個(gè)方法讓動(dòng)畫(huà)生效。
layoutSubviews:系統(tǒng)重寫(xiě)布局
setNeedsUpdateConstraints:告知需要更新約束,但是不會(huì)立刻開(kāi)始
updateConstraintsIfNeeded:告知立刻更新約束
updateConstraints:系統(tǒng)更新約束
而我們就可以在layoutIfNeeded里面獲取到空間的frame, 直接貼代碼:
- (void)layoutIfNeeded{
[super layoutIfNeeded];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:_thumbView.bounds byRoundingCorners:UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii:CGSizeMake(6, 6)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = _thumbView.bounds;//這里就可以獲取到我們想要的frame啦
maskLayer.path = maskPath.CGPath;
_thumbView.layer.mask = maskLayer;
}
效果如圖:

END
3.實(shí)現(xiàn)UITableView頭部彈彈的動(dòng)畫(huà)效果,
先上效果圖

其實(shí)就是檢測(cè)UIScrollView(當(dāng)然了,UITableView也是UIScrollView)滾動(dòng)到某個(gè)位置, 如果小于某個(gè)位置回彈, 大于某個(gè)位置展開(kāi), 當(dāng)然也可以換作自己的動(dòng)畫(huà)邏輯, 然后讓UIScrollView繼續(xù)平滑滾動(dòng), 滾動(dòng)到固定的位置, 使其定住. 注意這個(gè)平滑的要求, 有種方案就是設(shè)置UIScrollView的contentoff, 但這種方案是一下子滾動(dòng)到指定位置, 而不是平滑. 于是我們搜尋一下UIScrollView的代理方法:
@protocol UIScrollViewDelegate<NSObject>
@optional
- (void)scrollViewDidScroll:(UIScrollView *)scrollView; // any offset changes
- (void)scrollViewDidZoom:(UIScrollView *)scrollView NS_AVAILABLE_IOS(3_2); // any zoom scale changes
// called on start of dragging (may require some time and or distance to move)
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
// called on finger up if the user dragged. velocity is in points/millisecond. targetContentOffset may be changed to adjust where the scroll view comes to rest
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset NS_AVAILABLE_IOS(5_0);
// called on finger up if the user dragged. decelerate is true if it will continue moving afterwards
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView; // called on finger up as we are moving
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; // called when scroll view grinds to a halt
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView; // called when setContentOffset/scrollRectVisible:animated: finishes. not called if not animating
- (nullable UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; // return a view that will be scaled. if delegate returns nil, nothing happens
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view NS_AVAILABLE_IOS(3_2); // called before the scroll view begins zooming its content
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view atScale:(CGFloat)scale; // scale between minimum and maximum. called after any 'bounce' animations
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView; // return a yes if you want to scroll to the top. if not defined, assumes YES
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView; // called when scrolling animation finished. may be called immediately if already at top
@end
里面有個(gè)方法我們比較感興趣, 但平日里比較少用, 就是這個(gè):
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset;
// called on finger up if the user dragged. decelerate is true if it will continue moving afterwards
注釋里告訴我們, 滾動(dòng)完之后, 可以繼續(xù)移動(dòng). 而targetContentOffset就是我們想要的目標(biāo)偏移量, 里面的(inout CGPoint *)回調(diào)也比較奇怪, 字面意思大概就是可以當(dāng)作輸入輸出, 應(yīng)該是當(dāng)作一個(gè)指針當(dāng)作回傳, 告訴UIScrollView將要移動(dòng)到哪, 我們可以在targetContentOffset設(shè)置輸入量, 如下:
targetContentOffset->x = 0;
targetContentOffset->y = -104;
當(dāng)然這種設(shè)置并不完美, 在一些奇葩的拖動(dòng)會(huì)有鬼畜的效果, 于是我們對(duì)速度加入了約束:
//也就是速度為0 之后再做滾動(dòng)
if (velocity.y == 0) {
if ((-newY) > 100 && (-(newY) + 64) < self.headerView.sectionHeaderViewOrinY) {
targetContentOffset->x = 0;
targetContentOffset->y = -104;
}
}
于是就可以作出開(kāi)頭顯示的動(dòng)畫(huà)了.END
2.實(shí)現(xiàn)UITextField回刪按鈕的回調(diào)檢測(cè)
UITextField的代理事件只有下面幾種:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField;
- (void)textFieldDidBeginEditing:(UITextField *)textField;
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField;
- (void)textFieldDidEndEditing:(UITextField *)textField;
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;
- (BOOL)textFieldShouldClear:(UITextField *)textField;
- (BOOL)textFieldShouldReturn:(UITextField *)textField;
想要檢測(cè)回刪按鈕的事件時(shí), 發(fā)現(xiàn)沒(méi)有回調(diào)事件,看來(lái)需要我們自己來(lái)實(shí)現(xiàn)UITextField回刪按鈕的回調(diào).
首先點(diǎn)進(jìn)UITextField的實(shí)現(xiàn)
NS_CLASS_AVAILABLE_IOS(2_0) @interface UITextField : UIControl <UITextInput, NSCoding, UIContentSizeCategoryAdjusting>
實(shí)現(xiàn)了UITextInput的協(xié)議, 再點(diǎn)進(jìn)UITextInput的協(xié)議看看
protocol UITextInput <UIKeyInput>
@required
/* Methods for manipulating text. */
- (nullable NSString *)textInRange:(UITextRange *)range;
- (void)replaceRange:(UITextRange *)range withText:(NSString *)text;
里面的幾個(gè)方法沒(méi)有我們感興趣的, 繼續(xù)點(diǎn)進(jìn)UIKeyInput,可以看到
@protocol UIKeyInput <UITextInputTraits>
#if UIKIT_DEFINE_AS_PROPERTIES
@property(nonatomic, readonly) BOOL hasText;
#else
- (BOOL)hasText;
#endif
- (void)insertText:(NSString *)text;
- (void)deleteBackward;
@end
好了, 我們找到了- (void)deleteBackward;就是隱藏的刪除回調(diào)方法, 只是沒(méi)寫(xiě)在UITextField的回調(diào)方法里面而已. 因此我們可以寫(xiě)一個(gè)繼承UITextField的自定義類(lèi), 如下:
#import <UIKit/UIKit.h>
@protocol KeyboardInputDelegate <NSObject>
@optional
//我們自己定義的一個(gè)回刪按鈕的回調(diào)方法, 順便傳回UITextField
- (void)deleteBackword:(UITextField *)field;
@end
@interface DeleteDetectionTextField : UITextField
@property (nonatomic, weak) id<KeyboardInputDelegate> KeyboardInputDelegate;
@end
然后重寫(xiě)一下- (void)deleteBackward;方法
//這個(gè)就是被隱藏的回刪方法, 咱們覆寫(xiě)一下
- (void)deleteBackward{
[super deleteBackward];
if (self.KeyboardInputDelegate && [self.KeyboardInputDelegate respondsToSelector:@selector(deleteBackword:)]) {
//這句話會(huì)不會(huì)引發(fā)循環(huán)引用?
[self.KeyboardInputDelegate deleteBackword:self];
}
}
然后我們調(diào)用自定義的DeleteDetectionTextField使用時(shí), 記得設(shè)置一下代理:
inputField.delegate = self;
inputField.KeyboardInputDelegate = self;
實(shí)現(xiàn)回調(diào):
#pragma mark - 回刪按鈕的回調(diào)
- (void)deleteBackword:(UITextField *)field{
//這里就可以干羞羞的事情
}
完成
1.xib拖的控件不能用代碼作動(dòng)畫(huà),用代碼作動(dòng)畫(huà)會(huì)無(wú)端出現(xiàn)很多坑. 因此xib比較適合靜態(tài)視圖的搭建,一些動(dòng)畫(huà)視圖還是使用純代碼比較合適些