본문 바로가기
프로그래밍/Xcode-iOS

UIPickerView Dynamic Scrolling & Customize

by Mr-후 2020. 6. 11.
반응형

오늘은 간만에 기술블로그? ㅎㅎㅎ 

회사 서비스가 오픈을 준비하면서 밋밋했던 앱 메인의 배너를 조금 다이나믹(Dynamic)하게 애니메이션을 넣어보자는 기획의도가 발의되고 시안이 대략적으로 나왔는데 화려하거나 유려하지는 않지만 심심하지 않을정도의 애니메이션이 들어간 스크롤뷰를 만드는 작업이 생겼다. 

처음에는 깃허브나 리소스 사이트를 둘러보면서 비슷한 기능을 찾아보느라 시간을 소비했고 마땅히 커스터마이징해서 사용할만한 리소스가 없어 기존 컴포넌트중에 가장 유사한 UIPickView를 사용하는 예제와 UICollectionView와 UITableView를 사용하는 예를 고려해보았는데 UITableView로 만들어도 무난할 것 같다는 판단을 했지만 좀 더 심플한 UIPickerView를 이번에 한번 떧어 보자는 심산에 UIPickerView를 사용하기로 했다. 

 

기존의 UIPIckerView의 UI적인 요소를 걷어내고 내가 원하는 UI를 만들수 있나 찾아보게 되었고 가능하다는 확신과 함께 애니메이션을 접목해서 다음과 같은 기능을 하는 UIPIckerView를 만들게 되었다. 

 

 

 

기존 UIPIckerView의 배경색과 선택영역의 실선 두개를 숨김처리하고 투명하게 만들었고 뷰 하나를 UIPIckerView의 첫번째 SubView에다 addSubView를 해서 라운딩 된 선택영역을 커스터마이즈 했다.

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    
    if (_isDrawRect) {
        return;
    }
    
    _isDrawRect = YES;
    
    _pView.delegate = self;
    _pView.dataSource = self;
    [_pView setBackgroundColor:[UIColor clearColor]];
    
    UIView *v = [[UIView alloc] initWithFrame:CGRectMake(60, 41, 200, 38)];
    [v setBackgroundColor:[UIColor whiteColor]];
    v.layer.cornerRadius = v.frame.size.height/2;
    v.layer.borderWidth = 1;
    v.layer.borderColor = _colorWithHexRGB(0x3799D0).CGColor;
    [[[_pView subviews] firstObject] addSubview:v];
    
    NSArray *subViews = [_pView subviews];
    for (UIView *v  in subViews) { //pickerView 라인 숨김.
        if (v.frame.origin.y > 0) {
            [v setHidden:YES];
        }
    }
    
    //start timer
    [self timerTick:nil];
}

중요한건 UIView의 drawRect: 안에서 반드시 다시 draw를 해야한다.  또한 drawRect는 한번만 실행되도록 막는 것도 중요하다. 

다음은 UIPickerView의 DataSource 와 DataDelegate 메서드에 대한 부분, 데이터소스를 핸들링하는 방법에 대한 코드 예제다. 

- (void)awakeFromNib {
    [super awakeFromNib];
    _selectedIndex = -1;
    
    self.mWordList = [[NSMutableArray alloc] initWithArray:@[@"대중교통 이용할때", @"운전할때", @"자전거 탈때", @"에스컬레이터 탈때", @"골프치러 갈때"]];
    
    self.wordList = @[
        @"대중교통 이용할때",
        @"운전할때",
        @"자전거 탈때",
        @"에스컬레이터 탈때",
        @"골프치러 갈때"
    ];
}

NSMutableArray를 이용, 기본 데이터셋(wordList)을 특정 시점 (count -2)에 MutableArray 기본데이터셋을 추가해주면서 무한루프가 되는듯한 착칵이 일어나도록 꾸몄다. 아래 코드가 그 코드에 해당한다. 

    BOOL isAnimated = YES;
    if (_selectedIndex > ([_wordList count]-2)) {
        [_mWordList addObjectsFromArray:_wordList];
    }
    
    _selectedIndex++;
    [_pView selectRow:_selectedIndex inComponent:0 animated:isAnimated];

 

다음 UIPickerView의 DataSoruce Method와 DataDelegate Method 영역이다. 

#pragma mark - UIPickerViewDelegate

// returns the number of 'columns' to display.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    return 1;
}

// returns the # of rows in each component..
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
    return UINT16_MAX; //<---- UINT16_MAX는 65535의 숫자. 이것보다 더 큰 경우 정상동작안했다. 널 믿는다 iOS ^^ 
}

- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component {
    return 38; //row의 높이값 
}

//호출안됨
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
    return @"운전할 때";
}

//호출안됨
- (nullable NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component {
        NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
                                                   initWithString:@"운전할 때"
                                                   attributes:@{
                                                                NSFontAttributeName: [UIFont systemFontWithType:FONT_BODY of:10.0f],
                                                                NSForegroundColorAttributeName:_colorWithHexRGB(0x3d6cc2)
                                                                }];
    return (NSAttributedString *)attributedString;
}

//호출됨 : 여기에서 내가 원하는 row를 커스터마이징하면 된다. 
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(nullable UIView *)view {
    
    if ([_mWordList count] == 0) {
        return nil;
    }
    
    CGFloat _fontSize = 18.0f;
    UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, pickerView.frame.size.width-30, 38)];
    [title setText:_mWordList[row]];
    [title setFont:[UIFont systemFontWithType:FONT_MEDIUM of:_fontSize]];
    [title setTextColor:_colorWithHexRGB(0x0089CC)];
    [title setTextAlignment:NSTextAlignmentCenter];
    [title setBackgroundColor:[UIColor clearColor]];
    
    return title;
}
// 이전, 선택, 다음 리스트가 하나의 쌍으로 애니메이션 되면서 스크롤되어 올라간다. 

//selectRow을 하면 호출될줄알았지만 호출되지 않음 
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
    //NSLog(@" didSelectRow : row -> %ld", row);
}

 

이제 애니메이션 부분은 NSTimer를 이용해서 스크롤이 자동으로 되도록 selectedIndex값을 조절하도록 햇다. 

- (void)timerTick:(id)sender {
    if ([_mWordList count] == 0) {
        return;
    }
    
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
    }
    
    _timer = [NSTimer scheduledTimerWithTimeInterval:2.0f
                                              target:self
                                            selector:@selector(pickerViewAutoAnimated:)
                                            userInfo:nil
                                             repeats:YES];
}

- (void)pickerViewAutoAnimated:(NSTimer *)timer {
    if (_timer == nil || [_mWordList count] == 0) {
        return;
    }
    
    if (_selectedIndex > ([_wordList count]-2)) {
        [_mWordList addObjectsFromArray:_wordList];
    }
    
    _selectedIndex++;
    [_pView selectRow:_selectedIndex inComponent:0 animated:YES];
}

#pragma mark - public method
- (void)stopPickerViewAnimate {
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
    }
}

- (void)startPickerViewAnimate {
    [_mWordList removeAllObjects];
    [_mWordList addObjectsFromArray:_wordList];
    _selectedIndex = -1;
    [_pView selectRow:0 inComponent:0 animated:NO];
    
    [self timerTick:nil];
}

 

그리하여 나타난 배너 프로토타입이 완성되었다. 아이폰/안드로이드 모두 구현 가능하기 때문에 이 UIPickerView를 사용하기로 결정했다. 

 

이번에 이 작업을 해보면서 앱 개발에서 손을 뗀지가 넘 오래되었다는 느낌을 받았다. 인터넷에 보니 이제 아이폰 개발 서적은 많이 나오지도 않는다. 컴포넌트 사용하는 방법도 깜깜해지고 스위프트는 이제 머리속에서 다 지워진듯하다. @@ 

집에 가자마자 아이폰 서적들을 찾아보고 Essentials한 책 한권을 회사에 들고 왔다. 짬짬히 공부를 해두어야 익숙하지 않은 개발건에 대비할 수 있을 것 같다. 

 

 

반응형