💡 텍스트필드 안에 Picker 넣기

// MARK: - 사용할 picker 만들기
func createPicker() {
        // toolbar
        let toolbar = UIToolbar()
        toolbar.sizeToFit()
        
        // bar button
        let doneBtnPeriod = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(donePreseedPeriod))
        toolbar.setItems([doneBtnPeriod], animated: true)
        
        // assign toolbar
        alarmPeriodTextField.inputAccessoryView = toolbar
        
        // assign picker to the textField
        alarmPeriodTextField.inputView = periodPicker
        
        // datePicker mode
        alarmTimePicker.preferredDatePickerStyle = .wheels
        
    }

@objc func donePreseedPeriod() {
        self.view.endEditing(true)
        enableCompleteBtn()
    }
// MARK: - picker component 수정
extension InputDetailVC: UIPickerViewDataSource {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        3
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        if component == 0 {
            return 1
        }
        else {
            return 3
        }
    }
}

extension InputDetailVC: UIPickerViewDelegate {
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        if component == 0 {
            return "every"
        }
        else if component == 1 {
            return num[row]
        }
        return period[row]
}

💡 UISearchTextField

(개인정보는 모자이크 처리 했습니다!)

// MARK: - 검색한 후 필터링 된 데이터를 보여주기 위해 필요한 변수
var filteredData: [Friend]!
var nameTextFrom: String = ""
var phoneNumFrom: String = ""

//MARK: - UISearchBar 속성 변경 함수
func setSearchBar() {
        searchBar.placeholder = "친구 검색"
        searchBar.setImage(UIImage(named: "icn_search_box"), for: UISearchBar.Icon.search, state: .normal)
        searchBar.layer.borderWidth = 0
        searchBar.searchBarStyle = .minimal
        searchBar.setSearchFieldBackgroundImage(UIImage(named: "search_box"), for: .normal)
        searchBar.sizeToFit()
        searchBar.searchTextField.sizeToFit()
        searchBar.searchTextField.textColor = UIColor.black
        searchBar.searchTextField.font = UIFont.init(name: "NotoSansCJKKR-Regular", size: 14)
    }

// UISearchBar delegate
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        filteredData = []
        
				//검색창에 입력하지 않으면 tableView를 원래대로 보여준다
        if searchText == "" {
            filteredData = friendList
        }
        else {
            for friend in friendList {
								//cell의 변수가 searchText에 포함되면 
                if friend.name.contains(searchText) {
                    filteredData.append(contentsOf: [
                                            Friend(name: friend.name, phoneNumber: friend.phoneNumber, selected: friend.selected)])
                }
                else if friend.phoneNumber.contains(searchText) {
                    filteredData.append(contentsOf: [Friend(name: friend.name, phoneNumber: friend.phoneNumber, selected: friend.selected)])
                }
            }
        }
        self.tableView.reloadData()
    }


💡 전화가 걸렸는지 체크하는 방법

let callObserver = CXCallObserver()
var didDetectOutgoingCall = false

class PopUpContactVC: UIViewController {

		//MARK: - Call Material
    func showCallAlert() {
        guard let url = NSURL(string: "tel://" + UserDefaults.standard.string(forKey: "selectedFriendPhoneData")!),
              UIApplication.shared.canOpenURL(url as URL) else {
            return
        }
        
        callObserver.setDelegate(self, queue: nil)
        didDetectOutgoingCall = false
        //we only want to add the observer after the alert is displayed,
        //that's why we're using asyncAfter(deadline:)
        UIApplication.shared.open(url as URL, options: [:]) { [weak self] success in
            if success {
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                    self?.addNotifObserver()
                }
            }
        }
    }
    
    func addNotifObserver() {
        let selector = #selector(appDidBecomeActive)
        let notifName = UIApplication.didBecomeActiveNotification
        NotificationCenter.default.addObserver(self, selector: selector, name: notifName, object: nil)
    }
    
    @objc func appDidBecomeActive() {
        //if callObserver(_:callChanged:) doesn't get called after a certain time,
        //the call dialog was not shown - so the Cancel button was pressed
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
            if !(self?.didDetectOutgoingCall ?? true) {
                print("Cancel button pressed")
            }
        }
    }

	@IBAction func calling(_ sender: Any) {
	        showCallAlert()
	    }

}
//MARK: -Protocol Extension
/// 1
extension PopUpContactVC: CXCallObserverDelegate{
    func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
        /// 통화를 하게 되면
        if call.isOutgoing && !didDetectOutgoingCall {
            didDetectOutgoingCall = true
            
            guard let pvc = self.presentingViewController else {return}
            self.dismiss(animated: true){
                let storyBoard: UIStoryboard = UIStoryboard(name: "Review", bundle: nil)
                if let vc = storyBoard.instantiateViewController(withIdentifier: "ReviewVC") as? ReviewVC{
                    vc.modalPresentationStyle = .fullScreen
                    pvc.present(vc, animated: true, completion: nil)
                }
            }
            print("Call button pressed")
        }
    }
}

💡캘린더의 메모 뷰의 키워드, 리뷰 여부에 따른 메모 Height 변화

이슈1

isHidden 메소드를 이용하여 false로 해도 해당 뷰의 Space는 그대로 남아있었기 때문에 키워드, 리뷰가 없어도 첫 화면처럼 완성이 되지 않았었다.

하지만 StackView를 이용하여 해결했는데, StackView의 특성 상 내부의 Contents가 줄어들면서 StackView전체가 같이 줄어들기 때문에 아래의 코드처럼 Contraints 값을 조정했을 때 원하는 모습대로 뷰가 나올 수 있었다.

/// 키워드, 리뷰의 입력 여부에 따른 메모 뷰 크기 조정
    func memoHeightMode(idx: Int){
        /// 키워드 미입력 시
        if keywordForCV.count == 0 {
            calendarKeywordCollectionView.isHidden = true
            keywordCVTopAnchor.constant = 0
        }else{
            calendarKeywordCollectionView.isHidden = false
            keywordCVTopAnchor.constant = 14
        }
        /// 메모 미입력 시
        if fetchCalendar[idx].review.count < 1 || fetchCalendar[idx].review == "" {
            keywordCVBotAnchor.constant = 0
            memoTextLabel.isHidden = true
        }else{
            keywordCVBotAnchor.constant = 10
            memoTextLabel.isHidden = false
        }
        
        /// 키워드,메모 미입력 시
        if (fetchCalendar[idx].review.count < 1 || fetchCalendar[idx].review == "") && keywordForCV.count == 0  {
            memoBtnTopAnchor.constant = 22
            memoBtn.isHidden = true
        }else{
            memoBtnTopAnchor.constant = 17
            memoBtn.isHidden = false
        }
    }