iOS/iOS SeSAC 2기 TIL

iOS swift) 앱 런칭 중 고민했던 부분 & 해결 + 개발일지

Developer-Michelle 2022. 9. 14. 15:34

-투두 또는 체크리스트 X

-개발 공수 비교 및 추후 개선을 위한 회고

-기능 구현 시 이슈와 대응

 

-앱 소개: 서버 개발자와 협업한 '정리 꿀팁' 알려주는 앱 + 정리 일기 작성(버린 물건 사진으로 저장 / 정리의 비포 & 애프터 일기 저장)

-기술 스택: repository, 코드 베이스(snapkit), MVC, UIKit, AutoLayout ..

-오픈 소스: IQKeyBoardManager, Realm, Alamofire/SwiftyJson,  ..

-이터레이션 별 목표

-버그/이슈 및 대응/해결

9/8 목

5h

-TabbarController, VC1~4생성. UI 구성

Q. 의문점. Navigation 관련

TabBarController 파일은 생성했고, VC1~4생성은 했으나,

Navigation은 어디서 심어야하는 것인가?

그냥 각 VC마다 네비게이션 아이템 타이틀을 설정하면 자동으로 네비게이션이 달아지는 것일까?

A. TabBarController에서 Navigation을 심어준다. 각각 VC1-4 정의시. 그리고 SceneDelegate에서 TabbarController로 시작하게끔 코드를 구성.

 

-화면전환 조사

(다른 앱들은 Push-Pop / Modal 어느 용도로 썼는지 확인해보았다.

나의 경우 VC1, VC4 는 Modal 로 띄우고 VC2, VC3 은 Push-pop으로 띄우고 싶었는데 이럴 경우 (화면전환 방식을 섞어서 쓰면) 정신 없어질 것 같아서 고민)

(고양이책방: push-pop . modal은 없었음 / 요기요, 배달의민족: push-pop. 이벤트창만 modal.)

->사용자가 작성해야 하는 일기 화면의 경우 잘못 어딘가를 누르는 것을 방지하기 위해 present 방식이 낫다.

 

9/9 금

6h

tableview. alamofire & swiftyjson

서버 API 협업 시작. 

 

9/10 토

디자인, 기획 바꿈

 

9/11 일

6h

VC2 완성, VC3

 

9/13 화

* collectionview snapkit 으로 구성하는 부분에서 시간을 많이 투자함

-3열짜리를 만들어야 하는데 자꾸 2열로 나와서 애먹었음. -> 따로 글 작성

 

9/14 수

Q. 1번째 Tabbar controller 에서 2번째 Tabbar로 넘어갔다가 다시 1번째로 돌아가는 경우:

A. 원래 애플 정책상으로 1번째 Tabbar에서 최종적으로 이동했던 화면이 남어있는게 맞고,

만약에 1번째Tabbar의 1번째 화면으로 돌리고 싶다면, Tabbar delegate 관련 메서드를 사용

 

Q. YPImagePicker 도 사용 가능? 아니면 PHPController?

A. YPImagePicker도 사용 가능.

YPImagePicker를 push-pop 구조의 화면에서 사용하고 있었는데, 카메라를 찍기만하면 계속해서 TabbarController의 맨 처음 화면으로 돌아가는 현상이 있었다.

push-pop 구조의 화면에서 -> present 구조로 전환하니까 동작이 잘 되었다. +UXUI를 고려한 화면 구조인 것 같아 더 나은 방향인 듯.

 

*화면 전환이 굳이 모두 push-pop으로 할 필요는 없다.

어떤 경우에는(예를 들어 사용자가 무언가를 작성해야 하는 화면의 경우), 잘못 눌러서 이상한 화면으로 돌아갈 수도 있으니 present + fullscreen으로 하는 것이 좋을 수 있다.

 

*오늘 한 일: VC1에 Realm 적용

(file path url이 틀려서 시간이 오래걸림 - objectId "초기화" 주의) -> 따로 글 작성

 

9/15 목 - 이터레이션3 시작

이터레이션2 회고

이터레이션3 공수, 계획 재산정

- VC 1: Did Select Item At -> 수정화면 개발 시작

 

- VC 4 구조 - 테이블 뷰 안에 테이블뷰 셀, 컬렉션뷰, 컬렉션뷰 셀 만드는데 시간이 오래 걸림

(시간이 오래걸린 이유: 컬렉션 뷰 제약조건 어려워서 (snapkit으로 collectionview 만들기) 자꾸 사진이 겹쳐 나오거나 했음)

구조: BeforeAfterVC >> mainTableView >>  TableViewCell >> CollectionView >> CollectionViewCell

 

<File 별 정리>

  1. BeforeAfterVC >> mainTableView 
  2. BeforeAfterTableViewCell - collectionView 등록
  3. BeforeAfterCollectionViewCell 

*놓친 부분

TableView 에서 TableViewCell 등록,

=>TableViewCell 안에 collection view 등록을 하는데

이거는 mainVC의 CellForRowAt에서 

 

 cell.collectionView.delegate = self

 cell.collectionView.dataSource = self

 

CollectionView 에서 CollectionViewCell 등록

=>collectionView.register(BeforeAfterCollectionViewCell.self, forCellWithReuseIdentifier: "BeforeAfterCollectionViewCell")

 

근데 , 컬렉션뷰는 테이블뷰셀 안에서 등록되어 있음.

 

----------------------------------------------

 

컬렉션 뷰 안에 contentView, 그리고 그 안에 image, text를 넣는 구조.

 

컬렉션뷰 셀 안의 이미지, 텍스트 제약조건 주는 데에서 자꾸 문제가 생김.

- 다만, image, text를 넣는 과정에서 centerX.equalTo(contentView) 로 하는 방법이 맞는지 모르겠다.

- 그리고 이미지에 너비, 높이 고정값을 100으로 줬는데 기기별 대응에 적절한 방법일지? (어쩌면 테이블뷰 높이 값을 고정 값으로 주니깐 어떤 다른 기기로 실행시켜도 괜찮을 것 같다는 생각이 든다.)

 

9/16 금 

* VC4 : 위의 디자인 마음에 안들어서 UI 변경.

아무래도 방 사진이다 보니 2:1 느낌의 사진이 낫다.

테이블뷰 높이 고정값 170으로 주고, 컬렉션뷰 높이도 170으로 주어 꽉 채우게 했다. (고정값을 주는 것도 괜찮아보인다.)

-Image의 경우, contentMode를 .scaleToFill로 해야 했다. AspectFill로 하면 이상하게 됨

 

* cornerRadius가 여기저기서 반복되어서 Constants 그룹의 Design을 만들어줌

이걸 적용하는 방법을 알게됨

enum Constants {

    enum Design {

        static let cornerRadius: CGFloat = 8

        static let borderWidth: CGFloat = 1

    } 

}

 

func setupView() {

        layer.cornerRadius = Constants.Design.cornerRadius

        layer.borderWidth = Constants.Design.borderWidth

        layer.borderColor = Constants.BaseColor.border

    }

 

* VC1-1 값전달 (Navigation title: 버린 날짜 적용: date 이전 VC에서 불러옴)

- 신규화면/수정화면을 같이 사용하므로, '기본 이미지의 여부'에 따라 네비게이션 타이틀 설정.

신규화면 = 기본 이미지(camera). 수정화면 != 기본이미지 

if mainView.bigImageView.image != UIImage(named: "camera") {

            self.navigationItem.title = "버린 날짜: \(writtenDate)"

        }

 

- dateFormatter 사용 : Date로 값 전달 받아와서 String 형태로 변환 (맞는 형태)

근데 string으로 받아와서 오류가 나서 시간을 소비함.

self.navigationItem.title = "버린 날짜: \(writtenDate!.formatted("yyyy-MM-dd"))"

 

*VC 4 : 기획 추가

*VC4-1 UI구성


9/17 토

7h

*VC4-1 UI구성 중 --> 문제 겪은 부분: UIPickerView 안에 라벨을 넣는데 표시가 안되었음

https://ggasoon2.tistory.com/14

 

UIPickerView UI custom 하기

이렇게 생긴 기본 UIPickerView 를 이렇게 custom 해보도록 하겠습니다. 맨위의 기본 UIPickerView의 코드 입니다. 여기서 수정해나가겠습니다. // // TestViewController.swift // BusanWelfareProgram // // C..

ggasoon2.tistory.com

 

 

오류 원인:

1) layer is a part of cycle in its layer tree

layer.addSublayer(layer)

위에처럼 바보짓을 했다.

https://stackoverflow.com/questions/31553111/layer-is-a-part-of-cycle-in-its-layer-tree-ios-9-swift-2

 

layer is a part of cycle in its layer tree iOS 9 Swift 2

When I load my view controller I'm getting this error: *** Terminating app due to uncaught exception 'CALayerInvalid', reason: 'layer <CALayer: 0x7fda42c66e30> is a part of cycle in its layer...

stackoverflow.com

 

func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {

        let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 40))

//        let view = mainView.pickerView //이렇게 하면 안되었다. 이렇게 잘못했던 이유는 : 이 함수의 의미 자체를 몰랐다.

viewForRow 메소드 의미는 PickerView의 '행' 개념이었다. 나는 이게 PickerView 자체인줄 알아서 등록하려고 했던거.

//        mainView.pickerViewLabel.text = placeList[row]

        let placeLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 40))

        placeLabel.text = placeList[row]

        placeLabel.textAlignment = .center

        placeLabel.font = UIFont.systemFont(ofSize: 28, weight: .light)

        placeLabel.textColor = Constants.BaseColor.textcolor

        

        view.addSubview(placeLabel)

        return view

    }

 

2) 위의 함수 사용법을 정확하게 몰라서

HistoryDiaryView.swift 에서 pickerViewLabel 를 등록하고 있었다. 근데 사실 위에 델리게이트 메소드에서만 처리하면 되는거였음. (위 메소드에서 placeLabel)

 

정리하자면,

pickerView는 HistoryDiaryView.swift 에서 등록하고, 제약조건을 줌.

그리고 메인VC에서 delegate처리한 곳에서 pickerViewLabel 등록함.

 

3) viewDidLoad에서

self.mainView.pickerView.delegate = self 를 빼먹었음. 밑에서 extension delegate 실컷 하고 viewDidLoad에서 빼먹으면 안됨.

 


*UIPickerView에서

'기타' row 선택시 toggle 느낌으로 textfield hidden이었다가 튀어나오는 형태.

근데 문제는, 다시 다른 row로 돌아갈 때 textfield 다시 hidden 처리가 안됨.

viewWillAppear에서 아래 처리해줘도 안되었음..

if row != 6 {

            mainView.textField.isHidden = true

        }

 

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {

        print("select=\(row)")

        if row == 6 {

            mainView.textField.isHidden = false

        }

    }


*VC4-2 UI구성

*화면 구성 고민: VC4에서 present-> push -> dismiss


 

*VC4 Realm 관련 생각 정리: 결국 VC1에서 이미지 등록할 때와 비슷하다. 가져오는 데이터를 UI에 반영하는 것만 다를뿐.

1) Realm Model 새로 추가 어떤 식으로 할 것인가? 새로운 .swift 파일 생성? => Realm 그룹에 PhotoDiary, UserPhotoDiaryListRepository 추가.

 

2)

@Persisted var place: String

@Persisted var imageCount: Int => 추후에 여러장 등록될 수도 있으니까 대비

@Persisted var regDate = Date()

이미지는 예전처럼 objectId를 이용.

 

3) VC4-2 에서 '완료'버튼 누르면 ->

section 이름 = place , tableview안의 collectionViewCell의 image = 위의 image, date = regDate.

섹션 및 테이블이 추가되는 방식 -> 0번째부터 등록하는 걸로?

 

4) 한번에 한개의 이미지만 등록하는 걸로. 1 place - 1 image 매칭 : 추후에 업데이트 시 변경할 수도 있을 것 같다.


*VC2 UI 수정

UILabel 관련 이슈 2가지

1) UILabel - top-left 정렬 (자꾸 centerY 느낌으로 content Label 에 표시되었던걸 왼쪽 위로 딱 붙임)

해결 방법 : 

make.size.greaterThanOrEqualTo(titleLabel)

contentLabel.snp.makeConstraints { make in

            make.top.equalTo(titleLabel.snp.bottom).offset(30)

            make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(40)

            make.size.greaterThanOrEqualTo(titleLabel) // 타이틀라벨의 사이즈보다 크거나 같다.

//            make.height.equalTo(200) -> height 를 설정하면 위의 보라색 코드와 충돌. 그래서 지워주었음.

        }

 

https://skytitan.tistory.com/306

 

[iOS] UILabel의 text를 top-left 정렬하기

 UILabel을 top-left로 정렬하고자 bottom constraint를 하단에 superview 하단에 equal로 맞추게 되면  보이는 것과 같이 View의 수직방향 중앙에서 텍스트가 시작이 된다. 딱 2가지 작업만 해주면 된다. Firs..

skytitan.tistory.com

https://velog.io/@leeyoungwoozz/iOS-%EC%B4%88%EC%8B%AC%EC%9E%90%EC%9D%98-SnapKit-%EC%82%AC%EC%9A%A9%EA%B8%B0

 

[iOS] 초심자의 SnapKit 사용기

지난 1달 반 정도 프로젝트를 진행하면서, 처음으로 SnapKit 을 사용하게 되었다. 사용하면서도 "이게 줄어들면 얼마나 줄어든다고... 왜 쓰지?" 라는 생각을 했었는데, 우연히 옛날 레포를 살펴보

velog.io

 

2) UILabel 자간 설정 - 처음에 등록하는 lazy var UILabel에서 처리해주려고 했으나, contentLabel의 text가 생성되기 전이므로 옵셔널 오류가 났다. 따라서 아래처럼 함수를 생성해서 viewDidLoad에서 해당 함수 호출.

 

//MARK: - UILabel 안 행간 간격(자간)

    func addCharacterSpacinginUILabel() {

        let attrString = NSMutableAttributedString(string: contentLabel.text!)

        let paragraphStyle = NSMutableParagraphStyle()

        paragraphStyle.lineSpacing = 6

        attrString.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, attrString.length))

        contentLabel.attributedText = attrString

    }

https://developer-fury.tistory.com/37

 

[Swift] UILabel Text 행간격(행간) 늘리는 방법 - AttributedString

안녕하세요 :] 오늘 한 번 알아볼 것은 바로~!! UILabel Text의 행 간격을 조절하는 방법입니다. 저는 개인적으로 Text가 긴 내용이 들어가는 화면이라면 자간, 행간을 잘 조절해야 보기 좋은 화면이

developer-fury.tistory.com

 


9/18 일

*오류 발생 부분 - UIPickerView에서 row = 6 인 '기타' 선택시 textField 나타나게하고, 다시 다른 row 로 가면 없어지게 해야하는데

계속해서 textField가 남아있다.

->시도 방법: viewWillAppear X

-> pickerView의 didSelectRow 메서드 밖에서 자꾸 뭔가를 하려고 하면 row 인식이 안되어 쓸 수가 없었다.

따라서 메서드 내에서

mainView.textField.isHidden = row != 6 추가.

(즉, row가 6일 경우에만 textfield 나타나게.)

 

//아래의 코드를 위에처럼 간략하게 바꿈

//        if row == 6 {

//            mainView.textField.isHidden = false

//        } else {

//            mainView.textField.isHidden = true

//        }


*VC4-1 다음 버튼 클릭시 row 가 '기타' && textfield가 nil이 아닌 경우-> 넘어가는 거 가능하게.

그런데 nil이어도 계속 넘어가는 현상.

-> nil 말고 ""로 쓰니깐 해결됨

 if selectedRow == 6 && mainView.textField.text == "" {

            showAlertMessage(title: "장소를 입력해주세요.", button: "확인")

} else { //selectedRow가 6일 땐 textField의 내용 전달 // 아닐 땐 선택된row의 배열 요소 전달.

vc.pickedPlace = selectedRow == 6 ? mainView.textField.text! : placeList[selectedRow]

}

 

아래의 코드에서 위 코드로 간략하게 바꿈.

//vc.pickedPlace = placeList[selectedRow]

//          if selectedRow == 6 {

//                vc.pickedPlace = mainView.textField.text!

//            }

 


*VC4-1 ImageView 클릭되게 하는 방법(원래 button으로 했으나 image 속성 값이 없어보여서 imageview로 다시 돌림)

-> tapGestureRecognizer 이용

let tapGesture = UITapGestureRecognizer(target: self, action: #selector(imageViewClicked))

self.mainView.imageView.addGestureRecognizer(tapGesture)

self.mainView.imageView.isUserInteractionEnabled = true

https://etst.tistory.com/99


*VC4 realm 구성

 

VC4-2에서 realm에 저장하고, VC4 메인에서 fetch 해서 UI에 뿌려준다.

 

  1. 중복되지 않은 섹션(장소) - non-duplicated rows based on specific column (Realm의 특정 장소 열을 중복되지 않는 테이블뷰 행으로 구성) https://stackoverflow.com/questions/65293393/realm-query-to-count-number-of-unique-entries
  2. 섹션에 따라 데이터를 가져옴(regDate, image) - fetch data by specific column (특정 섹션에 대한 데이터 가져오기)

 

오류 겪었던 부분:

tableView 와 collectionView가 따로 노는 현상 때문에.

 

테이블뷰의 섹션(장소) 리스트 placeSectionList: [String] = [] 섹션 리스트 지정

이걸 collectionView.tag랑 연결시킴

 

collectionViewCell은 몇 번째 테이블뷰의 섹션이든 다 0,0으로 찍히게 됨

몇 번째 테이블 섹션인지 알려면, colletionView에 tag를 달아주어야

테이블 뷰 셀 메소드 안에서 collectionView.tag = indexPath.section

 

totalList에는

totalList.append(repository.fetchFilterbyPlace(place: placeSectionList[indexPath.section])) 로 섹션마다의 realm 정보를 가져온다.

 

오류 겪었던 부분 2:

컬렉션뷰셀 추가를 하고나니 이전꺼 초기화를 시키지 않으면 계속 append가 되는 현상

 

Original

 A1 A2

 B1 B2

 C1 C2

 

Append without initialization:

 A1 A2

 B1 B2

 C1 C2

 A1 A2

 B1 B2

 C1 C2

 D1

 

Append after init.

  A1 A2

  B1 B2

  C1 C2

  D1

 

따라서 viewDidAppear에서 초기화를 시켜줌

override func viewDidAppear(_ animated: Bool) {

        placeSectionList = repository.fetchSectionHeader()

        totalList = []

        mainTableView.reloadData()

    }


9/19 월

[UI 디자인 조금씩 수정 - 간격]

 

*VC2 테이블뷰 섹션 별 간격 띄우기 (heightForHeaderInSection 메소드 이용)

 

*VC2 테이블뷰 섹션 배경색 및 글씨색, font size 변경 (willDisplayHeaderView 메소드 이용)

 

*VC3-2 제품 사진 테두리 둥글게

//MARK: - imageView

    lazy var productImg: UIImageView = {

        let view = UIImageView()

        view.contentMode = .scaleAspectFill

        view.layer.cornerRadius = Constants.Design.cornerRadius

        view.clipsToBounds = true

        return view

    }()

보라색 코드 2줄을 안썼더니 계속 테두리 둥글게가 되지 않고 각져있었다. 2줄 추가하니까 바로 해결- 둥글게 나옴!

 

*VC3-2 컬렉션뷰 행간 간격 띄우기 (layout.minimumLineSpacing = 50) 이용

 

*VC2 SideMenu 적용하려고 하루종일 유튜브 보면서 새로운 프로젝트에 따라 만들어봄.

그런데 막상 내 앱에 적용하려고 하니 TabBarVC에 또 Navigation을 넣는 구조라... 다시 검색하고 찾아야될듯 하다.


9/20 화

*VC2 UIMenu 구현

아이폰 감성 살리는거로 치면 UIMenu가 제일 나은거 같다.

어제부터 SideMenu -> Pull-down button -> 을 거쳐 UIMenu까지 오게 되었다.... 후

그래두 특징들을 알 수 있어서 좋았음!

SideMenu의 경우, TabBarController의 VC 화면이 통째로 오른쪽으로 왔다갔다 하는데, 이게 보기 싫어서 채택하지 않음.

Pull-down button의 경우, 네비게이션 left item에 바로 삽입하기가 어려워서 버림.

내 VC에서 가장 적합한 UI는 UIMenu였던걸루.

 

Pull-down button

https://ios-development.tistory.com/560

 

[iOS - HIG] 51. Pull-Down Menus (풀다운 메뉴, UIMenu)

* Pull-Down Menu는 Context Menu와 유사하므로 주의: https://ios-development.tistory.com/540?category=980570 [iOS - HIG] 45. Context Menus Context Menus 상황에 맞는 메뉴를 제공하여 인터페이스를 복잡하..

ios-development.tistory.com

 

UIMenu

https://jwonylee.tistory.com/entry/uimenu

 

iOS: UIMenu 사용해보기

Apple의 미리 알림 앱 화면이다. 내비게이션 우측 상단 버튼을 탭 하면 해당 위치에 메뉴 목록이 나타난다. 지금 하고 있는 프로젝트에 이 메뉴를 적용하고 싶어서 찾아봤는데, ActionSheet를 사용하

jwonylee.tistory.com


9/21 수

*VC2 UIMenu 필터링 느낌으로 책상 클릭시 책상 테이블뷰 섹션 나오게 하는 것에서 고민.

Alamofire를 통해서 통신을 한 뒤, YoutubeSectionModel을 불러옴.

var section = YoutubeSectionModel(sectionTitle: section_title, youtubeList: youtube_list)

self.list.append(section)

self.fixedList.append(section)

까지 되어 있는 상황에서, list, fixedList 에 전체 정보를 다 가져온 상황.(섹션 타이틀별 유튜브 리스트).

 

여기서 UIMenu를 만들었고, 섹션 타이틀별로 새로운 리스트를 만드는 식으로 하나의 테이블 섹션씩 불러옴

 

이 때, list 는 빈 바구니 역할, fixedList 는 알맹이들을 for 문 안에서 필터링하는 식으로 돌려서 list 안에 담겨지는 역할.

 

이 때 fixedList라고 새로 배열을 만든 이유는? 

VC2에 접속하는 순간, 전체 list가 뜨기 때문에.

 

그 다음, 사용자가 UIMenu에서 클릭한 sectionTitle에 대한 youtube 리스트들을 보여주어야 함.

이 때 처음 실행되는 로직은 list = [ ] 초기화이고

사용자가 클릭한 title 에 대한 youtubelist는 fixedList이므로 이게 list 배열에 담기는 형태.

 

즉, 여러번 alamofire를 불러오면 낭비이므로 한번만 불러오는 게 좋으니깐. list 를 그대로 놔두고 이거를 초기화 시켜줌 list = [ ] 

그리고 따로 만들어준 fixedList에서 list 정보 중에 해당하는 sectionTitle(ex 신발장) 이 메소드에 매개변수로 넣어주는  title과 일치될 경우, 이 알맹이들을 list 에 append한다. 그리고 mainTableView를 reloadData시켜주면 해당되는 섹션에 대한 정보가 테이블뷰에 출력됨.

 

section = fixedList[0], fixedList[1], fixedList[2]... 이런식으로 담기는데, 각각의 fixedList에 담긴 sectionTitle과 메소드 매개변수로 받는 title이 일치하면 출력.

 

마치 아래와 같은 느낌.

var b = ["1", "2", "5", "4", "78"] -> fixedList

var a: [String] = [] -> List

        for i in b {

            a.append(i) // List안에 fixedList 넣는 구조

        }

 

 break쓰는 이유: 1000개가 있을 경우, 만약 같은 title에 해당하는 자료를 3번째에서 찾았다면, 나머지 997번 안 찾아도 됨.

 

 

*VC1-1

[deleteButton 처음화면: 없음 / 수정화면: 등장

- 헷갈렸던 부분: viewDidLoad 또는 viewWillAppear에서 해야할 것 같았으나, 그럴 필요 없이 +버튼을 클릭하거나, collectionViewCell을 클릭시 조치해주면 되었다.]

 

-> 세부 설명

 

1) 일기 쓰는 화면에서 처음 쓸 경우, 기록삭제 버튼 없음

: VC1 제일 처음화면의 등록 + 버튼 클릭 시 

/MARK: - 화면 전환 (버리는 물건 사진 등록 화면으로 이동)

    @objc func plusbuttonClicked() {

                let vc = ThingDiaryViewController()

                let navi = UINavigationController(rootViewController: vc)

                navi.modalPresentationStyle = .fullScreen

                vc.mainView.deleteButton.isHidden = true // 값전달처럼 일단 버튼을 숨긴상태에서 띄워줌

                self.present(navi, animated: true)

    }

 

2) 컬렉션뷰셀 클릭시(이미 쓴 일기 수정화면의 경우), 기록 삭제 버튼 등장

 //MARK: - 화면 전환 (수정 화면으로 이동)

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

        let vc = ThingDiaryViewController()

        let navi = UINavigationController(rootViewController: vc)

        vc.mainView.registerButton.setTitle("이미지 수정하기", for: .normal)

        vc.mainView.deleteButton.isHidden = false

        navi.modalPresentationStyle = .fullScreen

        self.present(navi, animated: true)

    }

 

*VC1-1 다이어리 작성화면 '완료' 버튼 클릭시 분기 처리

if 작성화면 -> 사진 없을 때 "사진을 선택해주세요"

if 수정화면 -> 이미 등록된 사진에서 사용자가 수정할 사진 선택하지 않은 경우 "사진을 수정해주세요"

 


9/22 목

*VC1-1 Realm delete 

PhotoVC(VC1)에서 collectionview -> didSelectItemAt

let vc = ThingDiaryViewController()

vc.data = tasks[indexPath.item] 값 전달

 

ThingDiaryVC에서

var data: UserThingDiary = UserThingDiary()

 

//MARK: - 삭제 버튼 클릭시

-핵심: handler에서 2개 이상의 action 처리 => 함수로 따로 빼서 함수 하나를 넣어줌.

    @objc func deleteBtnClicked() {

        let alert = UIAlertController(title: "정말 삭제하시겠습니까?", message: nil, preferredStyle: UIAlertController.Style.alert)

        let okAction = UIAlertAction(title: "예", style: .default, handler : {action in self.pressed()} )

        let cancel = UIAlertAction(title: "아니요", style: .destructive, handler : nil)

        alert.addAction(cancel)

        alert.addAction(okAction)

        present(alert, animated: true, completion: nil)

    }

    func pressed()

    {

        repository.delete(data: data)

        self.dismiss(animated: true)

    }

 

*VC1-1 Realm 수정

 //MARK: - 수정

    func modify(data: UserThingDiary, count: Int, content: String) {

        

        guard let modifiedData = localRealm.object(ofType: UserThingDiary.self, forPrimaryKey: data.objectId) else {

            print("UserThing not found")

            return

        }

        

        do {

            try self.localRealm.write {

                modifiedData.imageCount = count

                modifiedData.content = content

            }

        } catch let error {

            print(error)

        }

    }

 

 

https://www.mongodb.com/docs/realm/sdk/swift/crud/update/

 

CRUD - Update - Swift SDK — Realm

To update a property of an embedded object or a related object, modify the property with dot-notation or bracket-notation as if it were in a regular, nested object.

www.mongodb.com

오전에는 사진 + 내용 다 수정해서 업데이트 되는 형식으로 바꿨으나,

오후에 다시 내용만 수정되어 업데이트 되는 형식으로 바꿈 (인스타 스타일. 버린 물건 사진을 수정하는 일은 없을 것 같아서)

 

*UI 전면 수정. 

- 안드로이드 느낌이 너무 난다고 판단해서 따로 뺐던 버튼들을 UIMenu 이용해서 숨김.

Before
After

- swift TextField 수정 막는 코드 적용

vc.mainView.textView.isEditable = false

 

-UIMenu에서 수정버튼을 눌렀을 때만 textView 활성화되게끔 했음.

 

*VC1-1. UIMenu 수정버튼 클릭시 무엇이 문제길래 수정도 안되는데 그냥 dismiss되어 화면이 내려갈까..?


9/24 토

거의 하루종일 개발..

*VC2 WebViewVC 

스크롤뷰만 거의 3시간 넘게 헤맨듯..  나한테는 snapkit이 제일 어려운거 같다.

그래도 제일 도움 많이 받은 페이지: 아래 참조 #swift scrollview vertically programatically

 

오류 원인:

 

1) 세로스크롤 해야하는데 , 가로 스크롤로 자꾸 됨

해결 ->

핵심1) left, right 제약조건을 뺐고, top bottom을 고정시킴. 그리고 centerX, width값을 줬더니 해결.

핵심2) 거의 대부분의 사이트에서 contentView(Containerview)안에 title, content를 넣는게 아니라 scrollView에 넣으라고 했다. 그랬더니 해결됨. (contentview안에 속성들을 넣으면 해결이 안되었음)

핵심3) scrollview에 들어오는 마지막 내용(이번경우엔 contentLabel)의 경우,

make.bottom.equalToSuperview()

해줘야함.

 

 

2) 유튜브는 고정시킨 상태에서, 아래만 스크롤.

-> scrollview, contentview 모두 top부분을 videoplayer.snp.bottom에 맞추니 해결됨

 

아래중 첫번째 페이지의 도움을 제일 많이 받음.. 무조건 이거다..ㅠㅠ

https://devddong.tistory.com/4 

 

[IOS] Label의 text에따라 유동적인 ScrollView 만들기

프로젝트에서 이용약관을 보여줘야 하는 VC가 필요했다. 대부분 이용약관 text는 상당히 길다. 어떻게 표현할지 고민하다 ScrollView를 사용했는데 ScollView의 레이아웃을 잡는 과정을 포스팅 하려한

devddong.tistory.com

https://ios-development.tistory.com/868

https://furang-note.tistory.com/42

https://hryang.tistory.com/7

https://velog.io/@nnnyeong/iOS-UIScrollView-%EB%8B%A4%EB%A3%A8%EA%B8%B0-autolayout-programatically

 

 

*VC4 컬렉션뷰셀 클릭시 팝업 뷰 애니메이션 효과 만들기

2016년에 제작된 오래된 영상이라 문법이 바뀌어서 그대로 적용이 힘들었기에.. 시간이 엄청 오래 걸렸다. 삽질의 연속.

 

https://www.youtube.com/watch?v=kzdI2aiTX4k 


9/25 일

거의 하루종일 개발..

*어제 하던 VC4 애니메이션 효과 다 만듬... 삽질의 연속 -따로 글 작성

: 최대한 평평한 검정배경의 팝업 뷰를 만듬

-navigation bar 의 bar tint color 도 다크그레이로 하고 alpha 값 0.99 줘서 최대한 흐릿하게 함

-tabbar controller도 isHidden = true 처리

 

*VC4 삭제 버튼 구현 및 realm 삭제 반영

 

*VC4 index out of range 오류 수정

: 중간발표 때 오류났던 부분 수정

오류 난 이유:

totalList.append(repository.fetchFilterbyPlace(place: place)) 이거를 viewDidLoad, viewDidAppear 두번해서

(viewDidLoad에서 한번만 쓰니 해결됨).

 

*VC1, 4 최초 유저에게 띄우는 안내 화면 (realm totalList.count == 0 인 경우, 이미지를 띄워줌)

그런데 예를 들어서, 최초 유저가 1개의 이미지를 등록했다가 다시 삭제하는 경우 다시 0이 되어 버린다. 그래서 또 이 이미지가 나타나게 되는데...? 그냥 놔둬도 큰 이슈는 없을듯..?

 


9/26 월

*VC 5 설정화면 추가

TableView 그리는데 생각보다 시간 소요 많이 됨

이유: snapkit아직도 미숙한듯 ㅠ

width, height 값 다 주어도 안되는 경우엔, width, height를 없애고 top, leading, trailing, bottom을 주자

 

mainTableView.snp.makeConstraints { make in

            make.top.equalTo(myPageLabel).offset(40)

            make.leading.equalTo(self.safeAreaLayoutGuide.snp.leading).inset(8)

            make.trailing.equalTo(self.safeAreaLayoutGuide.snp.trailing).inset(8)

//            make.width.equalTo(500)

//            make.height.equalTo(700)

            make.bottom.equalTo(self.safeAreaLayoutGuide.snp.bottom).inset(30)

        }

 

9/27 화

 

*디자이너 협업 시작

UI 및 아이콘 상당 부분 갈아 엎음

- UX를 고려해야 해서 VC1의 경우, 카메라 먼저 띄우고 등록하는 방식으로 바꿈. (클릭 횟수 감소 효과)

- 힘들었던 지점 (@ VC1)

1) YPImagePicker 쓰고 난 뒤 화면 전환 ViewDidAppear에서 해야 함.

2) picker가 dismiss 되고나서, modal로 ThingDiaryVC 를 띄워주는데, 값 전달이 자꾸 안되어서 UserDefaults 이용함.

 

VC2 : 아이콘 클릭시 -> 테이블뷰로 연결


9/28 수

*디자이너 피드백 받은 후 UX를 고려하여 UI 화면 많이 바꿈...

-SF Symbols 관련 협의

Jack님으로부터 해당 앱에서 아이콘마다 iOS16+ 이상인 아이콘이 있다는걸 알게됨..

iOS15 버전부터로 대응해야해서 몇몇 아이콘들은 Assets로 넣는 수밖에 없을 것 같다.

 

- color 적용시 컬러코드는 Int값임

var colorList: [Int] = [0xE4E8EB, 0xD8E5EE, 0xBACBDC, 0xDAD4D0, 0xEFEBE8]

 

- navigation title font size change

firstNavigationController.navigationBar.titleTextAttributes = [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 20)]

 

9/29 목, 9/30 금 

 VC1 UI 전체 변경에 의해 다 뜯어고침

너무 많은 일이 있었으나... 생략.. 


10/1 월

앱 심사 완료!! ^^


10/2 일

*TabBar Delegate (A의 서브 메뉴 갔더라도 B 탭했다가 다시 돌아오면 메인으로 전환되게)

https://0urtrees.tistory.com/7

 

그런데 여기서 처리 안되는게 딱한가지 있었다.

VC2에서 컬렉션뷰의 각 place선택시마다 테이블뷰 바뀌었는데, 이거까지 바꿔주진 못함(왜냐면 VC전환이 아니라서)

 

따라서 UserDefaults써서 해결.

 

override func viewWillAppear(_ animated: Bool) {

        if !UserDefaults.standard.bool(forKey: "isSecondVCLoaded") && !fixedList.isEmpty {

            reloadAllSection()

            self.navigationItem.title = "정리 꿀팁 영상 추천" //네비게이션 타이틀도 함께 바꿔줌(컬렉션뷰 이름 넘어갈 때마다 같이 바뀌었기 때문)

            UserDefaults.standard.set(true, forKey: "isSecondVCLoaded")

        }

    }

 

fixedList는 Alamofire통신 한번할 때 viewdidLoad에서 한번만 불러와지므로, 해당 탭에서 다른 탭으로 갔다가 다시 돌아오면, fixedList는 채워져있는 상태.

 

*ViewDidAppear로 하면 네비게이션 타이틀이 2번 reload되는 것 같아서 viewWillAppear로 하니 해결됨.

 

TabBar Controller에서 UserDefaults setting.

override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {

        if selectedIndex == 1 {

            UserDefaults.standard.set(false, forKey: "isSecondVCLoaded")

        }

        guard let viewController = self.viewControllers?[self.selectedIndex] as? UINavigationController else { return }

        viewController.popToRootViewController(animated: false)

    }

 

*VC4-1 키보드 올리기/내리기 (UIPickerView 기타 선택시)

 row == 7 ? mainView.textField.becomeFirstResponder() : mainView.textField.resignFirstResponder()

 

*VC2 출처 및 고지사항 관련 -> Pan Modal로 띄움

- 해본 시도: 처음에 테이블뷰 말고 그냥으로 띄웠더니 거의 화면에 꽉찼다. 그래서 테이블뷰로 구조를 바꾸고 height를 지정해줌.

근데 이래도 문제가 되었던거는, 내가 애초에 YoutubeWebVC를 push-pop구조의 push로 띄웠기 때문에 PanModal dismiss할 때마다 자꾸 rootVC로 넘어가게 됨.(아예 최초화면인 YoutubeVC)

그래서 아예 처음 화면 전환을 present + fullscreen으로 바꿈 그랬더니 모든게 해결됨.

 

*PanModal snapkit 관련 힘들었던 부분

width 를 원래 safeAreaGuide기준으로 leading, trailing 각각 -4씩 했더니 계속 오류가 났고,

UIScreen.bounds.width - 8 해서 넣었더니 해결됨

 

*VC2 출처 및 고지사항 버튼 관련 snapkit 힘들었던 부분:

scrollView안에 title, contentLabel, copyRightBtn 다 넣는 구조로 바꿨는데,

copyrightBtn 을 오른쪽 scrollview로부터 trailing -4하면 안되었고,

하도 안나오니깐 이걸 그냥 맨 왼쪽으로 붙여서 contentLabel.leading 에 offset똑같이 붙여보니깐 나왔다.

 

앞으로 오른쪽에 정렬하는게 어려우면 일단 왼쪽으로 정렬해봤다가 다시 생각해보는걸로. 


10/3 월

*셀 재사용 문제

TableViewCell 에서 PrepareForReuse() 메소드를 이용해서 각 이미지, 타이틀, 컨텐츠 nil값을 주었다.

관련 글이 많아서 적용은 쉬웠으나, 그래도 셀 재사용 문제가 완전히 해결되지는 않음.

https://ios-development.tistory.com/659

아무래도 TableView 캐시를 이용한 로딩 방법 이걸 참고해서 해봐야 될 것 같다 ㅠㅠ

 

*VC5 부활시킴

메일보내기 기능 구현 다듬었음 / 개인정보처리방침 WKWebView로 띄움

오픈소스라이브러리

 

*Http 접근 허용하기

기본적으로 swift에서는 https://만 접근 허용. http:// 접근 차단

따라서 info.plist에서 ATS설정에서 Allow Arbitary Loads 의 Bool값을 YES로 만든다.

 

*VC3 상품정보에 가격 추가

 


10/4 화

*VC5 화면 전환 관련 문제 해결

설정탭 -> 오픈소스 라이선스 클릭하고 상세 페이지 -> 그다음에 다시 돌아올 때 이상함

A-> B-> C로 갔다가 다시 돌아올 때 C->B로 돌아가야하는데 C->A로 돌아오는 현상.

TabBarController 에서 문제였다. ViewWillAppear에서 navigation controller 심어서 문제가 생겼음.

-> 처음엔 addChild로 심은걸 의심했으나 이게 문제가 아니고 생명주기 문제였음.

2번째 탭에서도 push -> pop 돌아갈 때 제일 처음 VC가 자꾸 떠서 문제였는데, TabBar 고치니까 이거도 바로 해결됨.

 

*VC2 Youtube 셀 재사용문제 관련 -> PrepareForReuse() 메소드 써서 초기값 = nil로 해놨는데도

tableView reload시 셀 재사용 문제가 완벽히 해결되지 않는 문제 

-> 캐시 사용?? 하려고 Jack님께 질문했으나, 해당 탭에서 통신은 한번 밖에 일어나지 않아서, 소용 없을거라고 하셨다 -

그 대신 Kingfisher placeholderimage 이미지더미 이용해서 로딩 완전히 이루어질때까지 잠시 보여주는 것

또는,  indicator, skeletonview, 로딩 바 등을 이용하는 것을 추천해주심 (가장 쉽고 빠른 방법)

 


10/5 수

*VC2 Youtube 셀 재사용문제 관련 -> Kingfisher로 한방에 해결.

Imagedummy 써서 해보기도 했는데

Tableview cellforRowAt 에서 매번 DispatchQueue 로 image 불러오던걸 Kingfisher로 하니깐 Imagedummy 넣을 필요 없이 바로 해결 되었다. :)

이유는 모르겠으나.. (더 공부를 해야 알 듯...)

(이미지 더미는.. 최후의 수단인게 좋은거 같은게.. 막상 해보니깐 진짜 안예뻤다.. )

 

*VC1 카테고리 추가 (욕실, 냉장고) -> 필요없을거 같아서 안하려고 했는데 디자이너가 통일성을 주고 싶어해서 추가함

 

*VC1, VC4 toast message 띄우는거 삭제함 (이미지 최대 한개만 선택하도록 막아버림). 

여러개 선택하는 UI로 바꿀까도 고민했으나, 로직을 새로 다 짜야되고 사용자에게 클릭 횟수를 늘리는 꼴이라 안하기로.

 

*VC3 카테고리 추가 (VC1,2,3,4 모두의 통일성을 위해서 냉장고, 기타 추가)

- 서버개발자에게 부탁해서 서버 섹션도 업데이트

 

*VC4 fetch될 때 오류 발견.. 추가해도 반영이 안되어서 오류 수정

core animation view띄우고 삭제버튼을 누르는 순간, 거기서 처음유저에게 띄우는 exampleImg, exampleLabel hidden처리해주어야 함 (일반적인 화면 전환의 경우가 아니기 때문)

 

 


10/9 일

* UI 관련 업데이트 (버튼에 그림자 주기 등)

 


10/10 월

*복원, 백업

 

* Tab5 tableView separator style (singleline 인 경우), 셀을 선택할 때마다 어떤 경우에는 선이 있고, 어떤 경우에는 선이 없어지는 현상

=> 해결:

1) tableView Cell에서 

 override func prepareForReuse() {

        super.prepareForReuse()

        titleLabel.text = nil

    }

2)  cell.selectionStyle = .none 이거 한줄로 해결되어서 놀람...!

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "SettingTableViewCell", for: indexPath) as! SettingTableViewCell

        cell.selectionStyle = .none

        cell.titleLabel.text = items[indexPath.row]

        return cell

    }


10/11 화

 

디자이너가 지적한 부분

1) TabBar의 이미지(특히 Tab1의 이미지 윗부분이 경계선과 너무 붙어보인다고함)

=> 찾아보니까 원래 이게 디폴트 느낌인듯.

https://www.youtube.com/watch?v=6dctpl_n37I 이 영상 끝 부분에서도 보면 원래 좀 붙어보임

=> 그치만 일단 이미지 크기좀 줄임

 

*Tab 4

ProductVC image 정사각형으로 바꿈.

UIScreen의 width의 0.29 ~ 0.3 이 적절해서 비율로 맞춤.

 


10/14 금

* 어려웠던 부분: 화면전환을 사진 선택(YPImagePicker)을 거쳐서 해야하는 경우  (TrashVC -> YPImagePicker -> MemoVC), 사용자가 각 section마다 첫번째의 collectionviewcell 을 클릭시 memoVC에서 이미 그 section(category)가 선택된채로 띄워지는 것 구현.

화면전환 구조: TrashVC에서 YPImagePicker뜨고, 내려간 다음-> viewDidAppear에서 다시 MemoVC를 띄워주는 구조.

 

사용자가 사진을 선택하는 경우, UserDefaults로 감지해서 ->  encode한걸 decode해주고 TrashVC에 이미지 반영.

사용자가 사진을 선택하려다 취소하는 경우도 있으므로 일단 fetch로 감지.

 

TrashVC.swift

else 뒷부분 참조

장소에 대한 collectionView.tag를 안 다음, TrashVC와 MemoVC 모두에서 placelist 순서가 같으므로 이를 이용.

 

viewDidAppear에서 화면 전환시 MemoVC.selectedIndex = UserDefaults.standard.integer(forKey: "newPlace") 이렇게 값전달해줌.

즉, TrashVC의 collectionView.tag = MemoVC의 selectedIndex가 되는 셈.

 

*이 때, 전역변수처럼 쓰지 않고, UserDefaults를 이용한 이유? A뷰가 내려가고 C뷰가 다시 뜨는 화면 전환 구조가 있어서, 전역변수가 초기화되는 효과가 있기 때문이다.

 

MemoVC.swift 에서

var selectedIndex: Int = -1 (아무것도 선택되지 않았을 때, -1이라고 가정)

 var categorylist: [UIButton] = [ ] 이것도 함께 선언해둠.

 

그리고 categorylist에 각 버튼을 리스트형태로 선언.

selectedIndex 값 전달이 된 경우, 시스템상으로 categoryclick한 효과를 줌.

 


10/18 화

*Tab1 메인 카메라 클릭시 - 랜덤으로 다른 버튼 및 카테고리가 클릭되어있는 현상 수정

MemoVC.swift에서

var selectedIndex: Int = -1 선언.

 

categorylist = [othersBtn, clothesBtn, shoesBtn, deskBtn, kitchenBtn, babyBtn, fridgeBtn, bathBtn]

        

        textView.delegate = self

        

        if selectedIndex != -1 {

            categoryclicked(categorylist[selectedIndex])

        }

    }

    

    @objc func categoryclicked(_ sender: UIButton) {

        data.place = (sender.configuration?.title)!

        

        if sender != clickedBtn {

            if clickedBtn != nil {

                clickedBtn.layer.shadowColor = UIColor.white.cgColor

            }

            clickedBtn = sender

        }

 

이런식으로 구현했고,

메인에서 카메라 클릭시에는 카테고리 클릭된게 아니므로

selectedIndex = -1 인 형태가 실행되어야.

 

TrashVC.swift 에서

viewdidLoad에서

UserDefaults.standard.set(-1, forKey: "newPlace") 로 값을 저장.

 

viewDidAppear에서

MemoVC로 넘어가는 코드에서 

let vc = MemoViewController()

        vc.bigImageView.image = image

        vc.selectImageList.append(image!)

        vc.selectedIndex = UserDefaults.standard.integer(forKey: "newPlace")

        let navi = UINavigationController(rootViewController: vc)

        navi.modalPresentationStyle = .fullScreen

 navi.navigationBar.titleTextAttributes = [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 20)]

        self.present(navi, animated: true)

        

        UserDefaults.standard.removeObject(forKey: "image4thingdiary")

        UserDefaults.standard.set(-1, forKey: "newPlace")

 

 func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

        if indexPath.item < totalList[collectionView.tag].count {

            let vc = ThingDiaryViewController()

            let navi = UINavigationController(rootViewController: vc)

            vc.data = totalList[collectionView.tag][indexPath.item]

            vc.mainView.infoLabel.isHidden = true

            navi.modalPresentationStyle = .fullScreen

            self.present(navi, animated: true)

        } else {

            let newPlace = collectionView.tag

            UserDefaults.standard.set(newPlace, forKey: "newPlace")

            imageViewClicked()

    }

 

 

*Realm migration 수업시간 배운 내용 바탕으로 수정했음

(AppDelegate에서 하지 않았는데, AppDelegate에 썼음)

 


10/19 수

*Tab 3 버튼 클릭시 색상 수정

https://developer-michelle.tistory.com/entry/iOS-swift-addGestureRecognizer-%ED%81%B4%EB%A6%AD%EC%8B%9C-UILabel-%EC%83%89%EC%83%81-%EB%B3%80%ED%99%94

 

iOS swift addGestureRecognizer 클릭시 UILabel 색상 변화

런칭한 앱 업데이트 중 기록 -collectionview에서 collectionview 클릭시 해당 contentview에 있는 placeLabel 색상이 검정->textcolor로 바뀌면서 화면 전환. ShopVC.swift //클릭할 때 색깔이 칠해지는 부분 인..

developer-michelle.tistory.com