1. 이전 포스팅 확인하기

 

https://growingsaja.tistory.com/911

 

[Objective-C] 앱 만들기 입문 - 15 : 싱글톤 적용 및 기타 코드 정리

1. 이전 포스팅 확인하기 https://growingsaja.tistory.com/901 [Objective-C] 앱 만들기 입문 - 14 : 디자인 소폭 변경 예시 및 일정 조건 충족되는 데이터의 경우 배 1. 이전 포스팅 확인하기 https://growingsaja.tistor

growingsaja.tistory.com

 

 

 

 

 

2. 이전 포스팅에서 추후 해결해야한다고 언급한 포스팅

 

아래 포스팅의 7번 문항에서 이 문제를 언급했는데, 이번에 해결을 해봅니다.

 

https://growingsaja.tistory.com/892

 

[Objective-C] 앱 만들기 입문 - 5 : api call 후 return받은 데이터 사용하기

1. 이전 포스팅 확인하기 https://growingsaja.tistory.com/891 [Objective-C] 앱 만들기 입문 - 4 : json file 읽어서 저장된 데이터 사용하기 1. 이전 포스팅 확인하기 https://growingsaja.tistory.com/890 [Objective-C] 앱 만들

growingsaja.tistory.com

 

 

 

 

 

3. 1차 수정 사항

 

 - 데이터를 다시 입력해주는 클래스에서 뷰 또한 함께 다시 그려주기 때문에 발생하는 문제입니다.

레이블은 미리 그려두고, 이후에 변경되는 데이터에 맞게 데이터만 업데이트하도록 소스코드를 수정할 필요가 있습니다.

또한 이를 위해서는 일단 h 파일에서 변수들을 property로 선언해주고, m파일에서 기존 뷰를 삭제하는 소스코드를 삭제할 필요가 있습니다.

 

// vim Controller/PopluarAssetListVC.h

#import <UIKit/UIKit.h>

// SecondViewController라는 이름의 뷰 컨트롤러 클래스를 선언합니다.
@interface PopluarAssetListVC : UIViewController
@property (readwrite, strong, nonatomic) UIScrollView *scrollView;
@property (readwrite, strong, nonatomic) UIStackView *stackView;

@property (strong, nonatomic) UILabel *UILabel;
@property (strong, nonatomic) UILabel *liveUTCLabel;
@property (strong, nonatomic) UILabel *koreanFlagLabel;
@property (strong, nonatomic) UILabel *liveKSTLabel;
@property (strong, nonatomic) UILabel *cryptoNameLabel;
@property (strong, nonatomic) UILabel *cryptoPriceHighLabel;
@property (strong, nonatomic) UILabel *exchangeNameHighLabel;
@property (strong, nonatomic) UILabel *cryptoPriceLowLabel;
@property (strong, nonatomic) UILabel *exchangeNameLowLabel;
@property (strong, nonatomic) UILabel *maxPricePremiumPercentLabel;
@property (strong, nonatomic) UIImageView *exchangeLogoHighImage;
@property (strong, nonatomic) UIImageView *exchangeLogoLowImage;
@property (strong, nonatomic) UIImageView *maxPremiumTabHighExchangeImage;
@property (strong, nonatomic) UIImageView *maxPremiumTabLowExchangeImage;




@end

 

 - 삭제하는 코드

// vim PopluarAssetListVC.m

// ...

    // 기존 뷰 삭제
    for (UIView *subview in self.scrollView.subviews) {
        [subview removeFromSuperview];
    }
    
// ...

 

 - 그 결과, 일부 데이터들은 업데이트되며 수정이 되고

데이터를 새로 불러온 것으로 업데이트 되는 과정에서 보는 화면이 강제로 최상단 이동되는 문제 또한 해결했습니다.

 

하지만 뷰를 위에 계속 그리면서 얹는 상황이기에, 사용하는 메모리가 비정상적인 수준과 속도로 올라가고 있습니다. 즉 아직 뷰를 계속 새로 그리면서 stack으로 쌓고 있다는 것입니다.

 

 

 

 

 

4. 2차 수정 사항

 

 - h 파일

cardView로 for문 형태로 돌아가며 업데이트해야하는 데이터의 경우, property 선언시 NSMutableArray로 append하도록 합니다. 그래서 추후 i로 for문 돌릴때, 각 Array에서 UILabel 및 UIImageView를 가져와 text 혹은 image를 설정해줍니다.

 

 - m 파일

 UILabel과 UIImageView 모두 다시 그리지 않도록, viewDidLoad로 다 빼주었습니다. 데이터를 수정하는 부분은 하단에 있도록 유지했습니다.

 

// vim Controller/PopluarAssetListVC.h

#import <UIKit/UIKit.h>

// SecondViewController라는 이름의 뷰 컨트롤러 클래스를 선언합니다.
@interface PopluarAssetListVC : UIViewController
@property (readwrite, strong, nonatomic) UIScrollView *scrollView;
@property (readwrite, strong, nonatomic) UIStackView *stackView;

@property (strong, nonatomic) UILabel *UILabel;

@property (strong, nonatomic) UIView *topInfoTab;
@property (strong, nonatomic) UILabel *earthLabel;
@property (strong, nonatomic) UILabel *liveUTCLabel;
@property (strong, nonatomic) UILabel *koreanFlagLabel;
@property (strong, nonatomic) UILabel *liveKSTLabel;

@property (strong, nonatomic) NSMutableArray *cardViewList; // Label
@property (strong, nonatomic) NSMutableArray *cryptoNameLabelList; // Label

@property (strong, nonatomic) NSMutableArray *cryptoPriceHighLabelList; // Label
@property (strong, nonatomic) NSMutableArray *exchangeNameHighLabelList; // Label
@property (strong, nonatomic) NSMutableArray *exchangeLogoHighImageList; // ImageView
@property (strong, nonatomic) NSMutableArray *cryptoPriceLowLabelList; // Label
@property (strong, nonatomic) NSMutableArray *exchangeNameLowLabelList; // Label
@property (strong, nonatomic) NSMutableArray *exchangeLogoLowImageList; // ImageView

@property (strong, nonatomic) NSMutableArray *maxPricePremiumPercentLabelList; // Label
@property (strong, nonatomic) NSMutableArray *maxPremiumTabHighExchangeImageList; // ImageView
@property (strong, nonatomic) NSMutableArray *maxPremiumTabLowExchangeImageList; // ImageView



@end

 

// vim Controller/PopluarAssetListVC.m

#import <Foundation/Foundation.h>
#import "PopluarAssetListVC.h"
// api 통신 용도
#import "CRTConnect.h"
// default.json 데이터 읽기
#import "DefaultLoader.h"
// 현재 시간 가져오는 용도
#import "CRTDateTime.h"

@implementation PopluarAssetListVC {
    // json에서 가져온 popularList raw 데이터
    NSArray *popularList;
    // Spot 가격 정보 저장용 데이터
    NSMutableDictionary *popularSpotPriceList;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // viewDidLoad 메서드에서 스크롤 뷰를 초기화하고 설정합니다.
    // 스크롤 뷰 생성 및 초기화
    self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    self.scrollView.backgroundColor = [UIColor clearColor];
    [self.view addSubview:self.scrollView];
    
    // 스택 뷰 생성 및 초기화
    self.stackView = [[UIStackView alloc] initWithFrame:self.scrollView.bounds];
    self.stackView.axis = UILayoutConstraintAxisVertical;
    self.stackView.distribution = UIStackViewDistributionEqualSpacing;
    self.stackView.alignment = UIStackViewAlignmentFill;
    self.stackView.spacing = 0;
    [self.scrollView addSubview:self.stackView];
    
    // default.json의 popularList 데이터 가져오기
    [self loadPopularList];
    [self setPriceList];
    
    // 데이터를 표시할 레이블 생성
    // **************************************** [Start] 뷰 그리기 **************************************** //
    //카드뷰 배치에 필요한 변수를 설정합니다.
    // 카드 목록 나열될 공간 세팅
    // ****************************** //
    // 목록 상단 공백 margin 설정
    CGFloat cardViewTopStartMargin = 10.0;
    // 카드 목록의 좌우 공백 각각의 margin
    CGFloat horizontalMargin = 2.0;
    // 카드와 카드 사이의 공백
    CGFloat cardViewSpacing = 0.0;
    // 카드뷰 목록 노출 시작 x축 위치 자동 설정
    CGFloat cardViewXPosition = horizontalMargin;
    // ****************************** //
    // 카드 자체에 대한 세팅
    // 카드 높이 길이 (상하 길이) 설정
    CGFloat cardViewHeight = 44.0;
    // 카드 좌우 길이 phone size 참조하여 자동 조정
    CGFloat cardViewWidth = [UIScreen mainScreen].bounds.size.width - horizontalMargin * 2;
    // 카드뷰 안에 내용 들어가는 공간까지의 margin
    CGFloat basicMarginInCard = 4.0;
    CGFloat defaultFontSize = 16.0;
    // ****************************** //
    
    // **************************************** [Start] 최상단에 기타 정보 공간 **************************************** //
    self.topInfoTab = [[UIView alloc] initWithFrame:CGRectMake(cardViewXPosition, cardViewTopStartMargin - (cardViewSpacing + cardViewHeight), cardViewWidth, cardViewHeight)];
    // 국기 너비 길이 (좌우 길이) 설정
    CGFloat miniImange = 18.0;
    
    /* #################### 글로벌 지구 이모티콘 및 시간 설정 #################### */
    self.earthLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard, miniImange, 20)];
    self.earthLabel.text = @"🌍";
    // 표준 시간 = 그리니치 표준시 : 더블린, 에든버러, 리스본, 런던, 카사블랑카, 몬로비아
    self.liveUTCLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange, basicMarginInCard, cardViewWidth / 2, 20)];
    self.liveUTCLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
    
    /* #################### 한국 이모티콘 및 시간 설정 #################### */
    // 태극기
    self.koreanFlagLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard + cardViewHeight / 2, miniImange, 20)];
    self.koreanFlagLabel.text = @"🇰🇷";
    // 한국 시간
    self.liveKSTLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange, basicMarginInCard + cardViewHeight / 2, cardViewWidth / 2, 20)];
    self.liveKSTLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
    
    /* #################### cardView를 self.scrollView에 추가 #################### */
    // global 적용
    [self.topInfoTab addSubview:_earthLabel];
    [self.topInfoTab addSubview:_liveUTCLabel];
    // korea 적용
    [self.topInfoTab addSubview:_koreanFlagLabel];
    [self.topInfoTab addSubview:_liveKSTLabel];
    [self.scrollView addSubview:_topInfoTab];
    
    // **************************************** [Start] 카드뷰 목록 쭉 만들기 **************************************** //
    self.cardViewList = [@[] mutableCopy];
    self.cryptoNameLabelList = [@[] mutableCopy];
    self.cryptoPriceHighLabelList = [@[] mutableCopy];
    self.exchangeNameHighLabelList = [@[] mutableCopy];
    self.exchangeLogoHighImageList = [@[] mutableCopy];
    self.cryptoPriceLowLabelList = [@[] mutableCopy];
    self.exchangeNameLowLabelList = [@[] mutableCopy];
    self.exchangeLogoLowImageList = [@[] mutableCopy];
    self.maxPricePremiumPercentLabelList = [@[] mutableCopy];
    self.maxPremiumTabHighExchangeImageList = [@[] mutableCopy];
    self.maxPremiumTabLowExchangeImageList = [@[] mutableCopy];
    
    for (int i=0; i<popularList.count; i++) {
        /* #################### 카드뷰 기본 세팅 #################### */
        UIView *cardView = [[UIView alloc] initWithFrame:CGRectMake(cardViewXPosition, cardViewTopStartMargin + i * (cardViewSpacing + cardViewHeight), cardViewWidth, cardViewHeight)];
        
        // UILabel의 텍스트 색상 및 배경색 설정
        // 카드뷰 배경색을 설정합니다.
        if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
            // 다크모드인 경우
            cardView.backgroundColor = [UIColor blackColor];
            [self.view addSubview:cardView];
            // 카드 테두리 다크그레이색
            cardView.layer.borderColor = [UIColor darkGrayColor].CGColor;
        } else {
            // 라이트모드인 경우
            cardView.backgroundColor = [UIColor whiteColor];
            [self.view addSubview:cardView];
            // 카드 테두리 다크그레이색
            cardView.layer.borderColor = [UIColor lightGrayColor].CGColor;
        }
        
        // 카드 테두리 두께
        cardView.layer.borderWidth = 0.5;
        
        // 카드뷰 모서리를 둥글게 설정합니다. 조건 1
        cardView.layer.cornerRadius = 0.0;
        // cardView의 경계를 기준으로 내용물이 보이는 영역을 제한합니다. masksToBounds를 YES로 설정하면, cardView의 경계 밖에 있는 모든 내용물은 자르고 숨깁니다(클립 됩니다). 즉 뷰의 경계 값을 초과한 부분을 자르기 위해 masksToBounds를 YES로 설정합니다. 반면 masksToBounds가 NO인 경우(기본값)에는 뷰의 경계 밖에 있는 내용물이 그대로 보이게 됩니다.
        cardView.layer.masksToBounds = YES;
        
        // UILabel 객체를 생성합니다. 이 레이블은 암호화폐의 이름을 표시할 것입니다.
        // 따라서 CGRect를 사용하여 레이블의 위치와 크기를 설정하며, 왼쪽 위 모서리에서 시작합니다.
        UILabel *cryptoNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard, cardViewWidth / 4, 20)];
        
        cryptoNameLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
//        systemFontOfSize:defaultFontSize
        // 생성한 cryptoNameLabel을 cardView의 서브뷰로 추가합니다. 이렇게 함으로써 레이블이 카드 뷰에 표시됩니다.
        [_cryptoNameLabelList addObject:cryptoNameLabel];
        [cardView addSubview:cryptoNameLabel];
        
        /* #################### High spot 현재 가격, 거래소 정보 노출 라벨 세팅 #################### */
        // 암호화폐 가격 레이블을 생성하고 카드뷰에 추가합니다.
        // 가격위한 기본 셋
        UILabel *cryptoPriceHighLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 2, basicMarginInCard, cardViewWidth / 2, 20)];
        cryptoPriceHighLabel.textAlignment = NSTextAlignmentRight;
        cryptoPriceHighLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
        
        // 거래소 이름 및 로고 레이블을 생성하고 카드뷰에 추가합니다.
        // 거래소 이름을 위한 기본 셋
        UILabel *exchangeNameHighLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 4 + miniImange, basicMarginInCard, cardViewWidth/4, 20)];
        exchangeNameHighLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
        // 거래소 아이콘을 위한 기본 셋
        UIImageView *exchangeLogoHighImage = [[UIImageView alloc] initWithFrame:CGRectMake(cardViewWidth / 4, basicMarginInCard, miniImange, miniImange)];
        exchangeLogoHighImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        
        /* #################### Low spot 현재 가격, 거래소 정보 노출 라벨 세팅 #################### */
        // 암호화폐 가격 레이블을 생성하고 카드뷰에 추가합니다.
        // 가격위한 기본 셋
        UILabel *cryptoPriceLowLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 2, cardViewHeight/2, cardViewWidth / 2, 20)];
        cryptoPriceLowLabel.textAlignment = NSTextAlignmentRight;
        cryptoPriceLowLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
        
        // 거래소 이름 및 로고 레이블을 생성하고 카드뷰에 추가합니다.
        // 거래소 이름을 위한 기본 셋
        UILabel *exchangeNameLowLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 4 + miniImange, cardViewHeight/2, cardViewWidth/4, 20)];
        exchangeNameLowLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
        // 거래소 아이콘을 위한 기본 셋
        UIImageView *exchangeLogoLowImage = [[UIImageView alloc] initWithFrame:CGRectMake(cardViewWidth / 4, cardViewHeight/2, miniImange, miniImange)];
        exchangeLogoLowImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        
        /* #################### spot 프리미엄 노출 세팅 #################### */
        // 기본 셋
        UILabel *maxPricePremiumPercentLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, cardViewHeight/2, cardViewWidth / 8 + miniImange, 20)];
        maxPricePremiumPercentLabel.textAlignment = NSTextAlignmentRight;
        maxPricePremiumPercentLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
        // top rank 거래소 아이콘을 위한 기본 셋
        UIImageView *maxPremiumTabHighExchangeImage = [[UIImageView alloc] initWithFrame:CGRectMake(basicMarginInCard, cardViewHeight/2, miniImange, miniImange)];
        maxPremiumTabHighExchangeImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        // bottom rank 거래소 아이콘을 위한 기본 셋
        UIImageView *maxPremiumTabLowExchangeImage = [[UIImageView alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange + cardViewWidth / 8, cardViewHeight/2, miniImange, miniImange)];
        maxPremiumTabLowExchangeImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        
        
        
        
        
        
        
        
        
        
        // top rank spot
        [cardView addSubview:cryptoPriceHighLabel];
        [_cryptoPriceHighLabelList addObject:cryptoPriceHighLabel];
        [cardView addSubview:exchangeNameHighLabel];
        [_exchangeNameHighLabelList addObject:exchangeNameHighLabel];
        [cardView addSubview:exchangeLogoHighImage];
        [_exchangeLogoHighImageList addObject:exchangeLogoHighImage];
        
        // bottom rank spot
        [cardView addSubview:cryptoPriceLowLabel];
        [_cryptoPriceLowLabelList addObject:cryptoPriceLowLabel];
        [cardView addSubview:exchangeNameLowLabel];
        [_exchangeNameLowLabelList addObject:exchangeNameLowLabel];
        [cardView addSubview:exchangeLogoLowImage];
        [_exchangeLogoLowImageList addObject:exchangeLogoLowImage];
        
        // spot max premium % in spot
        [cardView addSubview:maxPricePremiumPercentLabel];
        [_maxPricePremiumPercentLabelList addObject:maxPricePremiumPercentLabel];
        [cardView addSubview:maxPremiumTabHighExchangeImage];
        [_maxPremiumTabHighExchangeImageList addObject:maxPremiumTabHighExchangeImage];
        [cardView addSubview:maxPremiumTabLowExchangeImage];
        [_maxPremiumTabLowExchangeImageList addObject:maxPremiumTabLowExchangeImage];
        
        // cardView를 self.scrollView에 추가합니다.
        [self.scrollView addSubview:cardView];
        // 레이블 세팅 완료된 cardView를 CardViewList에 넣기
        [self.cardViewList addObject: cardView];
    }
    
    // 상하 스크롤 최대치 자동 설정
    CGFloat contentHeight = popularList.count * (cardViewHeight + cardViewSpacing);
    self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width, contentHeight);
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    // NSTimer 생성 및 메서드 호출 설정 - 매 특정시간마다 호출
    [NSTimer scheduledTimerWithTimeInterval:1
                                     target:self
                                   selector:@selector(updateDataAndView)
                                   userInfo:nil
                                    repeats:YES];
}

// 데이터 가져오기 및 뷰 업데이트 코드
- (void)updateDataAndView {
    // **************************************** [Start] api 콜 준비 **************************************** //
    CRTConnect* tryApiCall = [[CRTConnect alloc] init];
    // **************************************** [Start] Binance 데이터 가져오기 **************************************** //
    NSString *apiURL = @"https://api.binance.com/api/v3/ticker/price";
    [tryApiCall fetchDataFromAPI:apiURL withCompletionHandler:^(NSDictionary *jsonResponse, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error.localizedDescription);
        } else {
            // 여기에서 jsonResponse를 가공 한 후 앱에서 사용하실 수 있습니다.
            // Binance는 api return data가 array
            NSArray* resultOfApi = (NSArray *)jsonResponse;
            
            // api로 받은 데이터 깔끔한 dictionary로 가공하기
            for (int i=0; i<resultOfApi.count; i++) {
                NSString *symbol = resultOfApi[i][@"symbol"];
                NSString *price = resultOfApi[i][@"price"];
                if ([price floatValue] >= 1000) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-6)];
                } else if ([price floatValue] >= 100) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-5)];
                } else if ([price floatValue] >= 1) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-4)];
                } else if ([price floatValue] >= 0.1) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-3)];
                } else if ([price floatValue] >= 0.01) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-2)];
                } else if ([price floatValue] >= 0.001) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-1)];
                } else {
                    // price값 그대로 가져다씁니다.
                }

                self->popularSpotPriceList[@"Binance"][symbol] = price;
            }
        }
    }];
    // **************************************** [End] Binance 데이터 가져오기 **************************************** //
    // **************************************** [Start] Bybit 데이터 가져오기 **************************************** //
    apiURL = @"https://api.bybit.com/spot/quote/v1/ticker/price";
    [tryApiCall fetchDataFromAPI:apiURL withCompletionHandler:^(NSDictionary *jsonResponse, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error.localizedDescription);
        } else {
            // 여기에서 jsonResponse를 가공 한 후 앱에서 사용하실 수 있습니다.
            // result 안의 value만 추출
            NSArray* resultOfApi = jsonResponse[@"result"];
            
            // api로 받은 데이터 깔끔한 dictionary로 가공하기
            for (int i=0; i<resultOfApi.count; i++) {
                NSString *symbol = resultOfApi[i][@"symbol"];
                NSString *price = resultOfApi[i][@"price"];
                self->popularSpotPriceList[@"Bybit"][symbol] = price;
            }
        }
    }];
    // **************************************** [End] Bybit 데이터 가져오기 **************************************** //
    // 메인 스레드에서만 UI 업데이트 수행
    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateView];
    });
}

// default.json 파일의 popularList 데이터 읽기
-(void) loadPopularList {
    // default.json의 popularList 불러오고 정상인지 1차 확인하기 = popularList에 있는 각 element들의 개수가 같은지 확인
    popularList = [DefaultLoader sharedInstance].popularList;
    int checkDefaultJsonFile = 0;
    for (int i=0; i<popularList.count-1; i++) {
        if ([popularList[i] count] == [popularList[i+1] count]) {
            checkDefaultJsonFile += 1;
        }
    }
    if (checkDefaultJsonFile == [popularList count]-1) {
        // default.json파일의 popularList 안에 있는 Array들 중 길이가 모두 같으면
        NSLog(@"%@", @"[INFO] default.json Check : Normal");
    } else {
        // default.json파일의 popularList 안에 있는 Array들 중 길이가 다른 것이 1개라도 있으면 WARN 출력 및 앱 dead
        NSLog(@"****************************************************");
        NSLog(@"[WARN] Check File : default.json - popularList");
        NSLog(@"****************************************************");
    }
}

// popularList에서 수집을 1건 이상이라도 할 예정인 거래소 목록을 실시간 호가 정보 데이터에 셋업하기
-(void) setPriceList {
    // 초기값 세팅 (안해주면 for문 돌면서 해당 dictionary에 데이터가 들어가지 않음)
    popularSpotPriceList = [@{} mutableCopy];
    for (int i=0; i<popularList.count; i++) {
        for (NSString *eachPopular in popularList[i][0]) {
            NSString *category = [[eachPopular componentsSeparatedByString:@"-"] objectAtIndex:0];
            NSString *exchange = [[eachPopular componentsSeparatedByString:@"-"] objectAtIndex:1];
            if ([category isEqual:@"spot"]) {
                popularSpotPriceList[exchange] = [@{} mutableCopy];
            }
        }
    }
}

-(void) updateView {
    // 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
    self.liveUTCLabel.text = [[[CRTDateTime alloc] init] NowUTC: @"yyyy-MM-dd (E) kk:mm:ss"];
    
    // 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
    self.liveKSTLabel.text = [[[CRTDateTime alloc] init] NowKST: @"yyyy-MM-dd (E) kk:mm:ss"];
    
    
    // **************************************** [Start] 카드뷰 데이터 업데이트 **************************************** //
    for (int i=0; i<popularList.count; i++) {
        // 기준 key인 symbol 변수 설정
        NSString *symbolKey = popularList[i][4];
        /* #################### 카드뷰 기본 세팅 #################### */
        UILabel *cryptoNameLabel = _cryptoNameLabelList[i];
        cryptoNameLabel.text = symbolKey;
        
        /* #################### 취급 상품 정리 및 데이터 가공 for 동일 상품에 대한 거래소간 가격 비교 기존 Array 제작 #################### */
        NSMutableArray *exchangeNameList = [@[] mutableCopy];
        // 특정 거래소에 있는지 확인
        for (NSString *item in popularList[i][0]) {
            NSString *category = [[item componentsSeparatedByString:@"-"] objectAtIndex:0];
            // spot인 상품의 거래소 목록 만들기
            if ([category isEqual: @"spot"]) {
                NSString *exchangeName = [[item componentsSeparatedByString:@"-"] objectAtIndex:1];
                [exchangeNameList addObject:exchangeName];
            }
        }
        
        /* #################### 누가 High로 배치될지, Low로 배치될지 로직 #################### */
        // 거래소 가격 비교해보고 탑랭크, 바텀랭크 지정
        NSString *topRankPriceExchange = @"";
        NSString *bottomRankPriceExchange = @"";
        // 가격 정보가 1개라도 있을 경우
        if ([popularSpotPriceList[exchangeNameList[0]] objectForKey:symbolKey] || [popularSpotPriceList[exchangeNameList[1]] objectForKey:symbolKey]) {
            if ([popularSpotPriceList[exchangeNameList[0]][symbolKey] floatValue] >= [popularSpotPriceList[exchangeNameList[1]][symbolKey] floatValue]) {
                // index 0번 거래소가 1번 거래소보다 더 크거나 같으면
                topRankPriceExchange = exchangeNameList[0];
                bottomRankPriceExchange = exchangeNameList[1];
            } else {
                // index 1번 거래소가 0번 거래소보다 더 크면
                topRankPriceExchange = exchangeNameList[1];
                bottomRankPriceExchange = exchangeNameList[0];
            }
        } else {
            // 가격 정보가 모두 없을 경우 (로딩중이거나 못불러오거나) 전부 0으로 노출
            topRankPriceExchange = exchangeNameList[0];
            bottomRankPriceExchange = exchangeNameList[1];
            popularSpotPriceList[topRankPriceExchange][symbolKey] = 0;
            popularSpotPriceList[bottomRankPriceExchange][symbolKey] = 0;
        }
        
        // ****** top price spot 거래소명 및 이미지 설정 ****** //
        ((UILabel *)_exchangeNameHighLabelList[i]).text = topRankPriceExchange;
        ((UIImageView *)_exchangeLogoHighImageList[i]).image = [UIImage imageNamed:[DefaultLoader sharedInstance].exchangeImage[topRankPriceExchange]];;
        ((UILabel *)_cryptoPriceHighLabelList[i]).text = popularSpotPriceList[topRankPriceExchange][symbolKey];
        
        // ****** bottom price spot 거래소 이미지 설정 ****** //
        ((UILabel *)_exchangeNameLowLabelList[i]).text = bottomRankPriceExchange;
        ((UIImageView *)_exchangeLogoLowImageList[i]).image = [UIImage imageNamed:[DefaultLoader sharedInstance].exchangeImage[bottomRankPriceExchange]];
        ((UILabel *)_cryptoPriceLowLabelList[i]).text = popularSpotPriceList[bottomRankPriceExchange][symbolKey];
        
        /* #################### 프리미엄 비교 #################### */
        float topPrice = [popularSpotPriceList[topRankPriceExchange][symbolKey] floatValue];
        float bottomPrice = [popularSpotPriceList[bottomRankPriceExchange][symbolKey] floatValue];
        // 소수점 둘째자리 수까지
        NSString *maxPremiumPercent = [NSString stringWithFormat:@"%.2f", (topPrice-bottomPrice)/bottomPrice*100];
        
        // 특정 값 이상 프리미엄 발생시, 텍스트에 배경색 입히기
        if ([maxPremiumPercent floatValue] >= 0.1) {
            // 0.1 이상인 경우 주황색
            NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:maxPremiumPercent];
            [attributedString addAttribute:NSBackgroundColorAttributeName
                                     value:[UIColor orangeColor]
                                     range:NSMakeRange(0, maxPremiumPercent.length)];
            // 특성 적용
            ((UILabel *)_maxPricePremiumPercentLabelList[i]).attributedText = attributedString;
        } else if ([maxPremiumPercent floatValue] >= 0.05) {
            // 0.05 이상인 경우 회색
            NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:maxPremiumPercent];
            // ****** 시스템 테마 설정에 따라 색 다르게 적용 ****** //
            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
                // 다크모드인 경우
                [attributedString addAttribute:NSBackgroundColorAttributeName
                                         value:[UIColor darkGrayColor]
                                         range:NSMakeRange(0, maxPremiumPercent.length)];
            } else {
                // 라이트모드인 경우
                [attributedString addAttribute:NSBackgroundColorAttributeName
                                         value:[UIColor lightGrayColor]
                                         range:NSMakeRange(0, maxPremiumPercent.length)];
            }
            // 특성 적용
            ((UILabel *)_maxPricePremiumPercentLabelList[i]).attributedText = attributedString;
        } else {
            ((UILabel *)_maxPricePremiumPercentLabelList[i]).text = maxPremiumPercent;
        }
        
        // ****** max premium spot 이미지 및 프리미엄 수치 설정 ****** //
        maxPremiumPercent = [maxPremiumPercent stringByAppendingString:@"%"];
        
        ((UIImageView *)_maxPremiumTabHighExchangeImageList[i]).image = [UIImage imageNamed:[DefaultLoader sharedInstance].exchangeImage[topRankPriceExchange]];
        ((UIImageView *)_maxPremiumTabLowExchangeImageList[i]).image = [UIImage imageNamed:[DefaultLoader sharedInstance].exchangeImage[bottomRankPriceExchange]];
        
    }
    // **************************************** [End] 카드뷰 목록 쭉 만들기 **************************************** //
    
}
@end

 

 

 

 

 

5. 2차 수정 사항 결과 확인 및 추가 보완 사항 체크

 

 아래 이미지에서 볼 수 있듯이, attribute로 글자에 배경색을 입히는 경우 최근 한번이라도 0.05 이상이었으면 회색, 최근 한번이라도 0.1 이상이었으면 주황색 배경이 칠해진 상태로 text 데이터만 변경됩니다. 즉, 특정 0.05 미만으로 줄어들경우, text를 update하는 과정에서 배경색 attribute를 삭제해줘야합니다. 해당 이슈 해결은 6번에서 진행합니다.

 

 

 

 

 

 

6. 3차 수정 사항

 

label 자체의 background에 입힌 색을 삭제한다면, 아래 코드와 같이 하여 투명한 기본 색으로 변경이 가능합니다.

((UILabel *)_maxPricePremiumPercentLabelList[i]).backgroundColor = [UIColor clearColor];

 

필자의 경우, 글자수 range만큼 배경색이 칠해지도록 attribute를 설정으로 진행했기에, 기존 text 입력칸 자체를 attribute로 변경했습니다.

 

// vim Controller/PopluarAssetListVC.m

#import <Foundation/Foundation.h>
#import "PopluarAssetListVC.h"
// api 통신 용도
#import "CRTConnect.h"
// default.json 데이터 읽기
#import "DefaultLoader.h"
// 현재 시간 가져오는 용도
#import "CRTDateTime.h"

@implementation PopluarAssetListVC {
    // json에서 가져온 popularList raw 데이터
    NSArray *popularList;
    // Spot 가격 정보 저장용 데이터
    NSMutableDictionary *popularSpotPriceList;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // viewDidLoad 메서드에서 스크롤 뷰를 초기화하고 설정합니다.
    // 스크롤 뷰 생성 및 초기화
    self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    self.scrollView.backgroundColor = [UIColor clearColor];
    [self.view addSubview:self.scrollView];
    
    // 스택 뷰 생성 및 초기화
    self.stackView = [[UIStackView alloc] initWithFrame:self.scrollView.bounds];
    self.stackView.axis = UILayoutConstraintAxisVertical;
    self.stackView.distribution = UIStackViewDistributionEqualSpacing;
    self.stackView.alignment = UIStackViewAlignmentFill;
    self.stackView.spacing = 0;
    [self.scrollView addSubview:self.stackView];
    
    // default.json의 popularList 데이터 가져오기
    [self loadPopularList];
    [self setPriceList];
    
    // 데이터를 표시할 레이블 생성
    // **************************************** [Start] 뷰 그리기 **************************************** //
    //카드뷰 배치에 필요한 변수를 설정합니다.
    // 카드 목록 나열될 공간 세팅
    // ****************************** //
    // 목록 상단 공백 margin 설정
    CGFloat cardViewTopStartMargin = 10.0;
    // 카드 목록의 좌우 공백 각각의 margin
    CGFloat horizontalMargin = 2.0;
    // 카드와 카드 사이의 공백
    CGFloat cardViewSpacing = 0.0;
    // 카드뷰 목록 노출 시작 x축 위치 자동 설정
    CGFloat cardViewXPosition = horizontalMargin;
    // ****************************** //
    // 카드 자체에 대한 세팅
    // 카드 높이 길이 (상하 길이) 설정
    CGFloat cardViewHeight = 44.0;
    // 카드 좌우 길이 phone size 참조하여 자동 조정
    CGFloat cardViewWidth = [UIScreen mainScreen].bounds.size.width - horizontalMargin * 2;
    // 카드뷰 안에 내용 들어가는 공간까지의 margin
    CGFloat basicMarginInCard = 4.0;
    CGFloat defaultFontSize = 16.0;
    // ****************************** //
    
    // **************************************** [Start] 최상단에 기타 정보 공간 **************************************** //
    self.topInfoTab = [[UIView alloc] initWithFrame:CGRectMake(cardViewXPosition, cardViewTopStartMargin - (cardViewSpacing + cardViewHeight), cardViewWidth, cardViewHeight)];
    // 국기 너비 길이 (좌우 길이) 설정
    CGFloat miniImange = 18.0;
    
    /* #################### 글로벌 지구 이모티콘 및 시간 설정 #################### */
    self.earthLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard, miniImange, 20)];
    self.earthLabel.text = @"🌍";
    // 표준 시간 = 그리니치 표준시 : 더블린, 에든버러, 리스본, 런던, 카사블랑카, 몬로비아
    self.liveUTCLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange, basicMarginInCard, cardViewWidth / 2, 20)];
    self.liveUTCLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
    
    /* #################### 한국 이모티콘 및 시간 설정 #################### */
    // 태극기
    self.koreanFlagLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard + cardViewHeight / 2, miniImange, 20)];
    self.koreanFlagLabel.text = @"🇰🇷";
    // 한국 시간
    self.liveKSTLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange, basicMarginInCard + cardViewHeight / 2, cardViewWidth / 2, 20)];
    self.liveKSTLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
    
    /* #################### cardView를 self.scrollView에 추가 #################### */
    // global 적용
    [self.topInfoTab addSubview:_earthLabel];
    [self.topInfoTab addSubview:_liveUTCLabel];
    // korea 적용
    [self.topInfoTab addSubview:_koreanFlagLabel];
    [self.topInfoTab addSubview:_liveKSTLabel];
    [self.scrollView addSubview:_topInfoTab];
    
    // **************************************** [Start] 카드뷰 목록 쭉 만들기 **************************************** //
    self.cardViewList = [@[] mutableCopy];
    self.cryptoNameLabelList = [@[] mutableCopy];
    self.cryptoPriceHighLabelList = [@[] mutableCopy];
    self.exchangeNameHighLabelList = [@[] mutableCopy];
    self.exchangeLogoHighImageList = [@[] mutableCopy];
    self.cryptoPriceLowLabelList = [@[] mutableCopy];
    self.exchangeNameLowLabelList = [@[] mutableCopy];
    self.exchangeLogoLowImageList = [@[] mutableCopy];
    self.maxPricePremiumPercentLabelList = [@[] mutableCopy];
    self.maxPremiumTabHighExchangeImageList = [@[] mutableCopy];
    self.maxPremiumTabLowExchangeImageList = [@[] mutableCopy];
    
    for (int i=0; i<popularList.count; i++) {
        /* #################### 카드뷰 기본 세팅 #################### */
        UIView *cardView = [[UIView alloc] initWithFrame:CGRectMake(cardViewXPosition, cardViewTopStartMargin + i * (cardViewSpacing + cardViewHeight), cardViewWidth, cardViewHeight)];
        
        // UILabel의 텍스트 색상 및 배경색 설정
        // 카드뷰 배경색을 설정합니다.
        if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
            // 다크모드인 경우
            cardView.backgroundColor = [UIColor blackColor];
            [self.view addSubview:cardView];
            // 카드 테두리 다크그레이색
            cardView.layer.borderColor = [UIColor darkGrayColor].CGColor;
        } else {
            // 라이트모드인 경우
            cardView.backgroundColor = [UIColor whiteColor];
            [self.view addSubview:cardView];
            // 카드 테두리 다크그레이색
            cardView.layer.borderColor = [UIColor lightGrayColor].CGColor;
        }
        
        // 카드 테두리 두께
        cardView.layer.borderWidth = 0.5;
        
        // 카드뷰 모서리를 둥글게 설정합니다. 조건 1
        cardView.layer.cornerRadius = 0.0;
        // cardView의 경계를 기준으로 내용물이 보이는 영역을 제한합니다. masksToBounds를 YES로 설정하면, cardView의 경계 밖에 있는 모든 내용물은 자르고 숨깁니다(클립 됩니다). 즉 뷰의 경계 값을 초과한 부분을 자르기 위해 masksToBounds를 YES로 설정합니다. 반면 masksToBounds가 NO인 경우(기본값)에는 뷰의 경계 밖에 있는 내용물이 그대로 보이게 됩니다.
        cardView.layer.masksToBounds = YES;
        
        // UILabel 객체를 생성합니다. 이 레이블은 암호화폐의 이름을 표시할 것입니다.
        // 따라서 CGRect를 사용하여 레이블의 위치와 크기를 설정하며, 왼쪽 위 모서리에서 시작합니다.
        UILabel *cryptoNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard, cardViewWidth / 4, 20)];
        
        cryptoNameLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
//        systemFontOfSize:defaultFontSize
        // 생성한 cryptoNameLabel을 cardView의 서브뷰로 추가합니다. 이렇게 함으로써 레이블이 카드 뷰에 표시됩니다.
        [_cryptoNameLabelList addObject:cryptoNameLabel];
        [cardView addSubview:cryptoNameLabel];
        
        /* #################### High spot 현재 가격, 거래소 정보 노출 라벨 세팅 #################### */
        // 암호화폐 가격 레이블을 생성하고 카드뷰에 추가합니다.
        // 가격위한 기본 셋
        UILabel *cryptoPriceHighLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 2, basicMarginInCard, cardViewWidth / 2, 20)];
        cryptoPriceHighLabel.textAlignment = NSTextAlignmentRight;
        cryptoPriceHighLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
        
        // 거래소 이름 및 로고 레이블을 생성하고 카드뷰에 추가합니다.
        // 거래소 이름을 위한 기본 셋
        UILabel *exchangeNameHighLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 4 + miniImange, basicMarginInCard, cardViewWidth/4, 20)];
        exchangeNameHighLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
        // 거래소 아이콘을 위한 기본 셋
        UIImageView *exchangeLogoHighImage = [[UIImageView alloc] initWithFrame:CGRectMake(cardViewWidth / 4, basicMarginInCard, miniImange, miniImange)];
        exchangeLogoHighImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        
        /* #################### Low spot 현재 가격, 거래소 정보 노출 라벨 세팅 #################### */
        // 암호화폐 가격 레이블을 생성하고 카드뷰에 추가합니다.
        // 가격위한 기본 셋
        UILabel *cryptoPriceLowLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 2, cardViewHeight/2, cardViewWidth / 2, 20)];
        cryptoPriceLowLabel.textAlignment = NSTextAlignmentRight;
        cryptoPriceLowLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
        
        // 거래소 이름 및 로고 레이블을 생성하고 카드뷰에 추가합니다.
        // 거래소 이름을 위한 기본 셋
        UILabel *exchangeNameLowLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 4 + miniImange, cardViewHeight/2, cardViewWidth/4, 20)];
        exchangeNameLowLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
        // 거래소 아이콘을 위한 기본 셋
        UIImageView *exchangeLogoLowImage = [[UIImageView alloc] initWithFrame:CGRectMake(cardViewWidth / 4, cardViewHeight/2, miniImange, miniImange)];
        exchangeLogoLowImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        
        /* #################### spot 프리미엄 노출 세팅 #################### */
        // 기본 셋
        UILabel *maxPricePremiumPercentLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, cardViewHeight/2, cardViewWidth / 8 + miniImange, 20)];
        maxPricePremiumPercentLabel.textAlignment = NSTextAlignmentRight;
        maxPricePremiumPercentLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
        // top rank 거래소 아이콘을 위한 기본 셋
        UIImageView *maxPremiumTabHighExchangeImage = [[UIImageView alloc] initWithFrame:CGRectMake(basicMarginInCard, cardViewHeight/2, miniImange, miniImange)];
        maxPremiumTabHighExchangeImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        // bottom rank 거래소 아이콘을 위한 기본 셋
        UIImageView *maxPremiumTabLowExchangeImage = [[UIImageView alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange + cardViewWidth / 8, cardViewHeight/2, miniImange, miniImange)];
        maxPremiumTabLowExchangeImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        
        // top rank spot
        [cardView addSubview:cryptoPriceHighLabel];
        [_cryptoPriceHighLabelList addObject:cryptoPriceHighLabel];
        [cardView addSubview:exchangeNameHighLabel];
        [_exchangeNameHighLabelList addObject:exchangeNameHighLabel];
        [cardView addSubview:exchangeLogoHighImage];
        [_exchangeLogoHighImageList addObject:exchangeLogoHighImage];
        
        // bottom rank spot
        [cardView addSubview:cryptoPriceLowLabel];
        [_cryptoPriceLowLabelList addObject:cryptoPriceLowLabel];
        [cardView addSubview:exchangeNameLowLabel];
        [_exchangeNameLowLabelList addObject:exchangeNameLowLabel];
        [cardView addSubview:exchangeLogoLowImage];
        [_exchangeLogoLowImageList addObject:exchangeLogoLowImage];
        
        // spot max premium % in spot
        [cardView addSubview:maxPricePremiumPercentLabel];
        [_maxPricePremiumPercentLabelList addObject:maxPricePremiumPercentLabel];
        [cardView addSubview:maxPremiumTabHighExchangeImage];
        [_maxPremiumTabHighExchangeImageList addObject:maxPremiumTabHighExchangeImage];
        [cardView addSubview:maxPremiumTabLowExchangeImage];
        [_maxPremiumTabLowExchangeImageList addObject:maxPremiumTabLowExchangeImage];
        
        // cardView를 self.scrollView에 추가합니다.
        [self.scrollView addSubview:cardView];
        // 레이블 세팅 완료된 cardView를 CardViewList에 넣기
        [self.cardViewList addObject: cardView];
    }
    
    // 상하 스크롤 최대치 자동 설정
    CGFloat contentHeight = popularList.count * (cardViewHeight + cardViewSpacing);
    self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width, contentHeight);
    
    // NSTimer 생성 및 메서드 호출 설정 - 매 특정시간마다 호출
    [NSTimer scheduledTimerWithTimeInterval:1
                                     target:self
                                   selector:@selector(updateDataAndView)
                                   userInfo:nil
                                    repeats:YES];
}

// 데이터 가져오기 및 뷰 업데이트 코드
- (void)updateDataAndView {
    // **************************************** [Start] api 콜 준비 **************************************** //
    CRTConnect* tryApiCall = [[CRTConnect alloc] init];
    // **************************************** [Start] Binance 데이터 가져오기 **************************************** //
    NSString *apiURL = @"https://api.binance.com/api/v3/ticker/price";
    [tryApiCall fetchDataFromAPI:apiURL withCompletionHandler:^(NSDictionary *jsonResponse, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error.localizedDescription);
        } else {
            // 여기에서 jsonResponse를 가공 한 후 앱에서 사용하실 수 있습니다.
            // Binance는 api return data가 array
            NSArray* resultOfApi = (NSArray *)jsonResponse;
            
            // api로 받은 데이터 깔끔한 dictionary로 가공하기
            for (int i=0; i<resultOfApi.count; i++) {
                NSString *symbol = resultOfApi[i][@"symbol"];
                NSString *price = resultOfApi[i][@"price"];
                if ([price floatValue] >= 1000) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-6)];
                } else if ([price floatValue] >= 100) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-5)];
                } else if ([price floatValue] >= 1) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-4)];
                } else if ([price floatValue] >= 0.1) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-3)];
                } else if ([price floatValue] >= 0.01) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-2)];
                } else if ([price floatValue] >= 0.001) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-1)];
                } else {
                    // price값 그대로 가져다씁니다.
                }

                self->popularSpotPriceList[@"Binance"][symbol] = price;
            }
        }
    }];
    // **************************************** [End] Binance 데이터 가져오기 **************************************** //
    // **************************************** [Start] Bybit 데이터 가져오기 **************************************** //
    apiURL = @"https://api.bybit.com/spot/quote/v1/ticker/price";
    [tryApiCall fetchDataFromAPI:apiURL withCompletionHandler:^(NSDictionary *jsonResponse, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error.localizedDescription);
        } else {
            // 여기에서 jsonResponse를 가공 한 후 앱에서 사용하실 수 있습니다.
            // result 안의 value만 추출
            NSArray* resultOfApi = jsonResponse[@"result"];
            
            // api로 받은 데이터 깔끔한 dictionary로 가공하기
            for (int i=0; i<resultOfApi.count; i++) {
                NSString *symbol = resultOfApi[i][@"symbol"];
                NSString *price = resultOfApi[i][@"price"];
                self->popularSpotPriceList[@"Bybit"][symbol] = price;
            }
        }
    }];
    // **************************************** [End] Bybit 데이터 가져오기 **************************************** //
    // 메인 스레드에서만 UI 업데이트 수행
    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateView];
    });
}

// default.json 파일의 popularList 데이터 읽기
-(void) loadPopularList {
    // default.json의 popularList 불러오고 정상인지 1차 확인하기 = popularList에 있는 각 element들의 개수가 같은지 확인
    popularList = [DefaultLoader sharedInstance].popularList;
    int checkDefaultJsonFile = 0;
    for (int i=0; i<popularList.count-1; i++) {
        if ([popularList[i] count] == [popularList[i+1] count]) {
            checkDefaultJsonFile += 1;
        }
    }
    if (checkDefaultJsonFile == [popularList count]-1) {
        // default.json파일의 popularList 안에 있는 Array들 중 길이가 모두 같으면
        NSLog(@"%@", @"[INFO] default.json Check : Normal");
    } else {
        // default.json파일의 popularList 안에 있는 Array들 중 길이가 다른 것이 1개라도 있으면 WARN 출력 및 앱 dead
        NSLog(@"****************************************************");
        NSLog(@"[WARN] Check File : default.json - popularList");
        NSLog(@"****************************************************");
    }
}

// popularList에서 수집을 1건 이상이라도 할 예정인 거래소 목록을 실시간 호가 정보 데이터에 셋업하기
-(void) setPriceList {
    // 초기값 세팅 (안해주면 for문 돌면서 해당 dictionary에 데이터가 들어가지 않음)
    popularSpotPriceList = [@{} mutableCopy];
    for (int i=0; i<popularList.count; i++) {
        for (NSString *eachPopular in popularList[i][0]) {
            NSString *category = [[eachPopular componentsSeparatedByString:@"-"] objectAtIndex:0];
            NSString *exchange = [[eachPopular componentsSeparatedByString:@"-"] objectAtIndex:1];
            if ([category isEqual:@"spot"]) {
                popularSpotPriceList[exchange] = [@{} mutableCopy];
            }
        }
    }
}

-(void) updateView {
    // 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
    self.liveUTCLabel.text = [[[CRTDateTime alloc] init] NowUTC: @"yyyy-MM-dd (E) kk:mm:ss"];
    
    // 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
    self.liveKSTLabel.text = [[[CRTDateTime alloc] init] NowKST: @"yyyy-MM-dd (E) kk:mm:ss"];
    
    
    // **************************************** [Start] 카드뷰 데이터 업데이트 **************************************** //
    for (int i=0; i<popularList.count; i++) {
        // 기준 key인 symbol 변수 설정
        NSString *symbolKey = popularList[i][4];
        /* #################### 카드뷰 기본 세팅 #################### */
        UILabel *cryptoNameLabel = _cryptoNameLabelList[i];
        cryptoNameLabel.text = symbolKey;
        
        /* #################### 취급 상품 정리 및 데이터 가공 for 동일 상품에 대한 거래소간 가격 비교 기존 Array 제작 #################### */
        NSMutableArray *exchangeNameList = [@[] mutableCopy];
        // 특정 거래소에 있는지 확인
        for (NSString *item in popularList[i][0]) {
            NSString *category = [[item componentsSeparatedByString:@"-"] objectAtIndex:0];
            // spot인 상품의 거래소 목록 만들기
            if ([category isEqual: @"spot"]) {
                NSString *exchangeName = [[item componentsSeparatedByString:@"-"] objectAtIndex:1];
                [exchangeNameList addObject:exchangeName];
            }
        }
        
        /* #################### 누가 High로 배치될지, Low로 배치될지 로직 #################### */
        // 거래소 가격 비교해보고 탑랭크, 바텀랭크 지정
        NSString *topRankPriceExchange = @"";
        NSString *bottomRankPriceExchange = @"";
        // 가격 정보가 1개라도 있을 경우
        if ([popularSpotPriceList[exchangeNameList[0]] objectForKey:symbolKey] || [popularSpotPriceList[exchangeNameList[1]] objectForKey:symbolKey]) {
            if ([popularSpotPriceList[exchangeNameList[0]][symbolKey] floatValue] >= [popularSpotPriceList[exchangeNameList[1]][symbolKey] floatValue]) {
                // index 0번 거래소가 1번 거래소보다 더 크거나 같으면
                topRankPriceExchange = exchangeNameList[0];
                bottomRankPriceExchange = exchangeNameList[1];
            } else {
                // index 1번 거래소가 0번 거래소보다 더 크면
                topRankPriceExchange = exchangeNameList[1];
                bottomRankPriceExchange = exchangeNameList[0];
            }
        } else {
            // 가격 정보가 모두 없을 경우 (로딩중이거나 못불러오거나) 전부 0으로 노출
            topRankPriceExchange = exchangeNameList[0];
            bottomRankPriceExchange = exchangeNameList[1];
            popularSpotPriceList[topRankPriceExchange][symbolKey] = 0;
            popularSpotPriceList[bottomRankPriceExchange][symbolKey] = 0;
        }
        
        // ****** top price spot 거래소명 및 이미지 설정 ****** //
        ((UILabel *)_exchangeNameHighLabelList[i]).text = topRankPriceExchange;
        ((UIImageView *)_exchangeLogoHighImageList[i]).image = [UIImage imageNamed:[DefaultLoader sharedInstance].exchangeImage[topRankPriceExchange]];;
        ((UILabel *)_cryptoPriceHighLabelList[i]).text = popularSpotPriceList[topRankPriceExchange][symbolKey];
        
        // ****** bottom price spot 거래소 이미지 설정 ****** //
        ((UILabel *)_exchangeNameLowLabelList[i]).text = bottomRankPriceExchange;
        ((UIImageView *)_exchangeLogoLowImageList[i]).image = [UIImage imageNamed:[DefaultLoader sharedInstance].exchangeImage[bottomRankPriceExchange]];
        ((UILabel *)_cryptoPriceLowLabelList[i]).text = popularSpotPriceList[bottomRankPriceExchange][symbolKey];
        
        /* #################### 프리미엄 비교 #################### */
        float topPrice = [popularSpotPriceList[topRankPriceExchange][symbolKey] floatValue];
        float bottomPrice = [popularSpotPriceList[bottomRankPriceExchange][symbolKey] floatValue];
        // 소수점 둘째자리 수까지
        NSString *maxPremiumPercent = [NSString stringWithFormat:@"%.2f", (topPrice-bottomPrice)/bottomPrice*100];
        
        // ****** max premium spot 이미지 및 프리미엄 수치 설정 ****** //
        maxPremiumPercent = [maxPremiumPercent stringByAppendingString:@"%"];
        
        // 특정 값 이상 프리미엄 발생시, 텍스트에 배경색 입히기
        NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:maxPremiumPercent];
        if ([maxPremiumPercent floatValue] >= 0.1) {
            // 0.1 이상인 경우 주황색
            [attributedString addAttribute:NSBackgroundColorAttributeName
                                     value:[UIColor orangeColor]
                                     range:NSMakeRange(0, maxPremiumPercent.length)];
        } else if ([maxPremiumPercent floatValue] >= 0.05) {
            // 0.05 이상인 경우 회색
            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
                // ****** 시스템 테마 설정에 따라 색 다르게 적용 ****** //
                // 다크모드인 경우
                [attributedString addAttribute:NSBackgroundColorAttributeName
                                         value:[UIColor darkGrayColor]
                                         range:NSMakeRange(0, maxPremiumPercent.length)];
            } else {
                // 라이트모드인 경우
                [attributedString addAttribute:NSBackgroundColorAttributeName
                                         value:[UIColor lightGrayColor]
                                         range:NSMakeRange(0, maxPremiumPercent.length)];
            }
        } else {
            // 일반 상태의 경우 clearColor로 지정해, 기존 색을 삭제
            [attributedString addAttribute:NSBackgroundColorAttributeName
                                     value:[UIColor clearColor]
                                     range:NSMakeRange(0, maxPremiumPercent.length)];
        }
        
        // 특성 적용
        ((UILabel *)_maxPricePremiumPercentLabelList[i]).attributedText = attributedString;
        
        ((UIImageView *)_maxPremiumTabHighExchangeImageList[i]).image = [UIImage imageNamed:[DefaultLoader sharedInstance].exchangeImage[topRankPriceExchange]];
        ((UIImageView *)_maxPremiumTabLowExchangeImageList[i]).image = [UIImage imageNamed:[DefaultLoader sharedInstance].exchangeImage[bottomRankPriceExchange]];
        
    }
    // **************************************** [End] 카드뷰 목록 쭉 만들기 **************************************** //
    
}
@end

 

 

 

 

 

 

 

+ Recent posts