1. 이전 포스팅 확인하기
https://growingsaja.tistory.com/902
2. 프로젝트 구조
Reference/scr 에 본 프로젝트에서만 유효하고 계속 가져다쓸 수 있는 메소드들 구현 예정
ex. 거래소별 icon image file name이 저장된 dictionary 제공 등 => ExchangeImage.h & ExchangeImage.m
3. ExchangeImage 파일 수정
// vim ExchangeImage.h
@interface ExchangeImage : NSObject
+(NSDictionary *) getImageFileNameAndExchangeName;
@end
// vim ExchangeImage.m
// Foundation 프레임워크를 임포트합니다. Foundation은 많은 기본적인 Objective-C 클래스와 인터페이스를 포함하고 있습니다.
#import <Foundation/Foundation.h>
#import "ExchangeImage.h"
@implementation ExchangeImage
+(NSDictionary *) getImageFileNameAndExchangeName {
NSDictionary *exchangeImageFileInfo = @{
@"Binance": @"exchangeBinance.png",
@"Bybit": @"exchangeBybit.jpeg"
};
return exchangeImageFileInfo;
}
@end
4. PopluarAssetListVC 파일 수정
- 미사용 변수 삭제, 코드 간결화
// vim Controller/PopluarAssetListVC.m
#import <Foundation/Foundation.h>
#import "PopluarAssetListVC.h"
// api 통신 용도
#import "CRTConnect.h"
// default.json 파일 읽기 용도
#import "CRTJSONReader.h"
// 현재 시간 가져오는 용도
#import "CRTSystem.h"
// ****** custom ****** //
// 거래소 별 이미지 파일명 매치
#import "ExchangeImage.h"
@implementation PopluarAssetListVC {
// json에서 가져온 popularList raw 데이터
NSMutableArray *popularList;
// Spot 가격 정보 저장용 데이터
NSMutableDictionary *popularSpotPriceList;
// 거래소, Spot 공통 참조 데이터
NSMutableDictionary *customSetting;
}
- (void)viewDidLoad {
[super 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];
[self loadCustomSetting];
// 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"];
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];
});
}
-(void) loadCustomSetting {
customSetting = [@{} mutableCopy];
customSetting[@"exchangeImageFileName"] = [ExchangeImage getImageFileNameAndExchangeName];
NSLog(@"%@", customSetting);
}
// default.json 파일의 popularList 데이터 읽기
-(void) loadPopularList {
// default.json의 popularList 불러오고 정상인지 1차 확인하기 = popularList에 있는 각 element들의 개수가 같은지 확인
CRTJSONReader *tryReadingDefaultJsonFile = [[CRTJSONReader alloc] init];
NSDictionary *defaultData = [tryReadingDefaultJsonFile loadJSONFromFile:@"default"];
popularList = defaultData[@"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 {
// **************************************** [Start] 뷰 그리기 **************************************** //
// 기존 뷰 삭제
for (UIView *subview in self.scrollView.subviews) {
[subview removeFromSuperview];
}
// viewDidLoad 메서드에서 스크롤 뷰를 초기화하고 설정합니다.
self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
self.scrollView.backgroundColor = [UIColor clearColor];
[self.view addSubview:self.scrollView];
//카드뷰 배치에 필요한 변수를 설정합니다.
// 카드 목록 나열될 공간 세팅
// ****************************** //
// 목록 상단 공백 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] 최상단에 기타 정보 공간 **************************************** //
UIView *topInfoTab = [[UIView alloc] initWithFrame:CGRectMake(cardViewXPosition, cardViewTopStartMargin - (cardViewSpacing + cardViewHeight), cardViewWidth, cardViewHeight)];
// 국기 너비 길이 (좌우 길이) 설정
CGFloat miniImange = 18.0;
/* #################### 글로벌 지구 이모티콘 및 시간 설정 #################### */
UILabel *earthLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard, miniImange, 20)];
earthLabel.text = @"🌍";
// 표준 시간 = 그리니치 표준시 : 더블린, 에든버러, 리스본, 런던, 카사블랑카, 몬로비아
UILabel *liveGMTLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange, basicMarginInCard, cardViewWidth / 2, 20)];
// 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
liveGMTLabel.text = [[CRTSystem alloc] init].NowDateTimeUTC;
liveGMTLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
/* #################### 한국 이모티콘 및 시간 설정 #################### */
// 태극기
UILabel *koreanFlagLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard + cardViewHeight / 2, miniImange, 20)];
koreanFlagLabel.text = @"🇰🇷";
// 한국 시간
UILabel *liveKSTLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange, basicMarginInCard + cardViewHeight / 2, cardViewWidth / 2, 20)];
// 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
liveKSTLabel.text = [[CRTSystem alloc] init].NowDateTimeKST;
liveKSTLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
/* #################### cardView를 self.scrollView에 추가 #################### */
// global 적용
[topInfoTab addSubview:earthLabel];
[topInfoTab addSubview:liveGMTLabel];
// korea 적용
[topInfoTab addSubview:koreanFlagLabel];
[topInfoTab addSubview:liveKSTLabel];
[self.scrollView addSubview:topInfoTab];
// **************************************** [Start] 카드뷰 목록 쭉 만들기 **************************************** //
for (int i=0; i<popularList.count; i++) {
// 기준 key인 symbol 변수 설정
NSString *symbolKey = popularList[i][4];
/* #################### 카드뷰 기본 세팅 #################### */
UIView *cardView = [[UIView alloc] initWithFrame:CGRectMake(cardViewXPosition, cardViewTopStartMargin + i * (cardViewSpacing + cardViewHeight), cardViewWidth, cardViewHeight)];
// 카드 테두리 다크그레이색
cardView.layer.borderColor = [UIColor darkGrayColor].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의 텍스트 색상 및 배경색 설정
// 카드뷰 배경색을 설정합니다.
if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
// 다크모드인 경우
cardView.backgroundColor = [UIColor blackColor];
[self.view addSubview:cardView];
} else {
// 라이트모드인 경우
cardView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:cardView];
}
// UILabel 객체를 생성합니다. 이 레이블은 암호화폐의 이름을 표시할 것입니다.
// 따라서 CGRect를 사용하여 레이블의 위치와 크기를 설정하며, 왼쪽 위 모서리에서 시작합니다.
UILabel *cryptoNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard, cardViewWidth / 4, 20)];
// 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
cryptoNameLabel.text = symbolKey;
cryptoNameLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
// systemFontOfSize:defaultFontSize
// 생성한 cryptoNameLabel을 cardView의 서브뷰로 추가합니다. 이렇게 함으로써 레이블이 카드 뷰에 표시됩니다.
[cardView addSubview:cryptoNameLabel];
/* #################### High spot 현재 가격, 거래소 정보 노출 라벨 세팅 #################### */
// 암호화폐 가격 레이블을 생성하고 카드뷰에 추가합니다.
// 가격위한 기본 셋
UILabel *cryptoPriceHighLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 3, basicMarginInCard, cardViewWidth / 2, 20)];
cryptoPriceHighLabel.textAlignment = NSTextAlignmentRight;
cryptoPriceHighLabel.font = [UIFont fontWithName:@"Pretendard-ExtraBold" 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 / 3, cardViewHeight/2, cardViewWidth / 2, 20)];
cryptoPriceLowLabel.textAlignment = NSTextAlignmentRight;
cryptoPriceLowLabel.font = [UIFont fontWithName:@"Pretendard-ExtraBold" 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; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
// 안에 들어가는 문구
// 특정 거래소에 있는지 확인
if ([popularList[i][0] containsObject:@"spot-Binance"]) {
// ****** 거래소 이미지 설정 ****** //
exchangeNameHighLabel.text = @"Binance";
exchangeLogoHighImage.image = [UIImage imageNamed:@"exchangeBinance.png"];
// Binance에 특정 코인 있는 경우
if ([popularSpotPriceList[@"Binance"] objectForKey:symbolKey]) {
// ****** 가격 노출값 설정 ****** //
cryptoPriceHighLabel.text = popularSpotPriceList[@"Binance"][symbolKey];
// 없으면 - 출력
} else {
cryptoPriceHighLabel.text = @"-";
}
}
if ([popularList[i][0] containsObject:@"spot-Bybit"]) {
// ****** 거래소 이미지 설정 ****** //
exchangeNameLowLabel.text = @"Bybit";
exchangeLogoLowImage.image = [UIImage imageNamed:@"exchangeBybit.jpeg"];
// Bybit에 특정 코인 있는 경우
if ([popularSpotPriceList[@"Bybit"] objectForKey:symbolKey]) {
// ****** 가격 노출값 설정 ****** //
cryptoPriceLowLabel.text = popularSpotPriceList[@"Bybit"][symbolKey];
// 없으면 - 출력
} else {
cryptoPriceLowLabel.text = @"-";
}
}
[cardView addSubview:cryptoPriceHighLabel];
[cardView addSubview:exchangeNameHighLabel];
[cardView addSubview:exchangeLogoHighImage];
[cardView addSubview:cryptoPriceLowLabel];
[cardView addSubview:exchangeNameLowLabel];
[cardView addSubview:exchangeLogoLowImage];
// cardView를 self.scrollView에 추가합니다.
[self.scrollView addSubview:cardView];
}
// **************************************** [End] 카드뷰 목록 쭉 만들기 **************************************** //
// 상하 스크롤 최대치 자동 설정
CGFloat contentHeight = popularList.count * (cardViewHeight + cardViewSpacing);
self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width, contentHeight);
}
@end
5. 다음 목표 기획 설정
위에 Binance, 아래에 Bybit 가격이 나오는 것이 아니라
spot 호가가 더 높은 거래소를 위에
spot 호가가 더 낮은 거래소를 아래에
-> 노출하도록 수정 진행
a. 거래소명, 거래소이미지, 가격 모두 함께 움직여야함.
b. 추후 Binance, Bybit 뿐만 아니라 더 늘어날 것을 대비해 둘만을 비교하는 것이 아니라, 거래소가 더 많아져도 최대, 최소 거래소를 찾아내어 노출하는 기능이 추후 추가될 수 있음을 어느정도는 감안해 구현
6. 해당 기획 반영한 PopluarAssetListVC 파일 수정
// vim Controller/PopluarAssetListVC.m
#import <Foundation/Foundation.h>
#import "PopluarAssetListVC.h"
// api 통신 용도
#import "CRTConnect.h"
// default.json 파일 읽기 용도
#import "CRTJSONReader.h"
// 현재 시간 가져오는 용도
#import "CRTSystem.h"
// ****** custom ****** //
// 거래소 별 이미지 파일명 매치
#import "ExchangeImage.h"
@implementation PopluarAssetListVC {
// json에서 가져온 popularList raw 데이터
NSMutableArray *popularList;
// Spot 가격 정보 저장용 데이터
NSMutableDictionary *popularSpotPriceList;
// 거래소, Spot 공통 참조 데이터
NSMutableDictionary *customSetting;
}
- (void)viewDidLoad {
[super 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];
[self loadCustomSetting];
// 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"];
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];
});
}
-(void) loadCustomSetting {
customSetting = [@{} mutableCopy];
customSetting[@"exchangeImageFileName"] = [ExchangeImage getImageFileNameAndExchangeName];
NSLog(@"%@", customSetting);
}
// default.json 파일의 popularList 데이터 읽기
-(void) loadPopularList {
// default.json의 popularList 불러오고 정상인지 1차 확인하기 = popularList에 있는 각 element들의 개수가 같은지 확인
CRTJSONReader *tryReadingDefaultJsonFile = [[CRTJSONReader alloc] init];
NSDictionary *defaultData = [tryReadingDefaultJsonFile loadJSONFromFile:@"default"];
popularList = defaultData[@"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 {
// **************************************** [Start] 뷰 그리기 **************************************** //
// 기존 뷰 삭제
for (UIView *subview in self.scrollView.subviews) {
[subview removeFromSuperview];
}
// viewDidLoad 메서드에서 스크롤 뷰를 초기화하고 설정합니다.
self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
self.scrollView.backgroundColor = [UIColor clearColor];
[self.view addSubview:self.scrollView];
//카드뷰 배치에 필요한 변수를 설정합니다.
// 카드 목록 나열될 공간 세팅
// ****************************** //
// 목록 상단 공백 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] 최상단에 기타 정보 공간 **************************************** //
UIView *topInfoTab = [[UIView alloc] initWithFrame:CGRectMake(cardViewXPosition, cardViewTopStartMargin - (cardViewSpacing + cardViewHeight), cardViewWidth, cardViewHeight)];
// 국기 너비 길이 (좌우 길이) 설정
CGFloat miniImange = 18.0;
/* #################### 글로벌 지구 이모티콘 및 시간 설정 #################### */
UILabel *earthLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard, miniImange, 20)];
earthLabel.text = @"🌍";
// 표준 시간 = 그리니치 표준시 : 더블린, 에든버러, 리스본, 런던, 카사블랑카, 몬로비아
UILabel *liveGMTLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange, basicMarginInCard, cardViewWidth / 2, 20)];
// 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
liveGMTLabel.text = [[CRTSystem alloc] init].NowDateTimeUTC;
liveGMTLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
/* #################### 한국 이모티콘 및 시간 설정 #################### */
// 태극기
UILabel *koreanFlagLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard + cardViewHeight / 2, miniImange, 20)];
koreanFlagLabel.text = @"🇰🇷";
// 한국 시간
UILabel *liveKSTLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange, basicMarginInCard + cardViewHeight / 2, cardViewWidth / 2, 20)];
// 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
liveKSTLabel.text = [[CRTSystem alloc] init].NowDateTimeKST;
liveKSTLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
/* #################### cardView를 self.scrollView에 추가 #################### */
// global 적용
[topInfoTab addSubview:earthLabel];
[topInfoTab addSubview:liveGMTLabel];
// korea 적용
[topInfoTab addSubview:koreanFlagLabel];
[topInfoTab addSubview:liveKSTLabel];
[self.scrollView addSubview:topInfoTab];
// **************************************** [Start] 카드뷰 목록 쭉 만들기 **************************************** //
for (int i=0; i<popularList.count; i++) {
// 기준 key인 symbol 변수 설정
NSString *symbolKey = popularList[i][4];
/* #################### 카드뷰 기본 세팅 #################### */
UIView *cardView = [[UIView alloc] initWithFrame:CGRectMake(cardViewXPosition, cardViewTopStartMargin + i * (cardViewSpacing + cardViewHeight), cardViewWidth, cardViewHeight)];
// 카드 테두리 다크그레이색
cardView.layer.borderColor = [UIColor darkGrayColor].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의 텍스트 색상 및 배경색 설정
// 카드뷰 배경색을 설정합니다.
if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
// 다크모드인 경우
cardView.backgroundColor = [UIColor blackColor];
[self.view addSubview:cardView];
} else {
// 라이트모드인 경우
cardView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:cardView];
}
// UILabel 객체를 생성합니다. 이 레이블은 암호화폐의 이름을 표시할 것입니다.
// 따라서 CGRect를 사용하여 레이블의 위치와 크기를 설정하며, 왼쪽 위 모서리에서 시작합니다.
UILabel *cryptoNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard, cardViewWidth / 4, 20)];
// 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
cryptoNameLabel.text = symbolKey;
cryptoNameLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
// systemFontOfSize:defaultFontSize
// 생성한 cryptoNameLabel을 cardView의 서브뷰로 추가합니다. 이렇게 함으로써 레이블이 카드 뷰에 표시됩니다.
[cardView addSubview:cryptoNameLabel];
/* #################### High spot 현재 가격, 거래소 정보 노출 라벨 세팅 #################### */
// 암호화폐 가격 레이블을 생성하고 카드뷰에 추가합니다.
// 가격위한 기본 셋
UILabel *cryptoPriceHighLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 3, basicMarginInCard, cardViewWidth / 2, 20)];
cryptoPriceHighLabel.textAlignment = NSTextAlignmentRight;
cryptoPriceHighLabel.font = [UIFont fontWithName:@"Pretendard-ExtraBold" 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 / 3, cardViewHeight/2, cardViewWidth / 2, 20)];
cryptoPriceLowLabel.textAlignment = NSTextAlignmentRight;
cryptoPriceLowLabel.font = [UIFont fontWithName:@"Pretendard-ExtraBold" 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; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
// 취급 상품 정리 및 데이터 가공
NSMutableArray *exchangeNameList = [@[] mutableCopy];
// 특정 거래소에 있는지 확인
for (NSString *item in popularList[i][0]) {
NSString *category = [[item componentsSeparatedByString:@"-"] objectAtIndex:0];
if ([category isEqual: @"spot"]) {
NSString *exchangeName = [[item componentsSeparatedByString:@"-"] objectAtIndex:1];
[exchangeNameList addObject:exchangeName];
}
}
// 거래소 가격 비교해보고 탑랭크, 바텀랭크 지정
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 거래소명 및 이미지 설정 ****** //
exchangeNameHighLabel.text = topRankPriceExchange;
exchangeLogoHighImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][topRankPriceExchange]];
cryptoPriceHighLabel.text = popularSpotPriceList[topRankPriceExchange][symbolKey];
// ****** bottom price spot 거래소 이미지 설정 ****** //
exchangeNameLowLabel.text = bottomRankPriceExchange;
exchangeLogoLowImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][bottomRankPriceExchange]];
cryptoPriceLowLabel.text = popularSpotPriceList[bottomRankPriceExchange][symbolKey];
[cardView addSubview:cryptoPriceHighLabel];
[cardView addSubview:exchangeNameHighLabel];
[cardView addSubview:exchangeLogoHighImage];
[cardView addSubview:cryptoPriceLowLabel];
[cardView addSubview:exchangeNameLowLabel];
[cardView addSubview:exchangeLogoLowImage];
// cardView를 self.scrollView에 추가합니다.
[self.scrollView addSubview:cardView];
}
// **************************************** [End] 카드뷰 목록 쭉 만들기 **************************************** //
// 상하 스크롤 최대치 자동 설정
CGFloat contentHeight = popularList.count * (cardViewHeight + cardViewSpacing);
self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width, contentHeight);
}
@end
7. 결과물 예시