할말은 주석에 다 써놨다.

// import Combine 까먹지 말것
import UIKit
import Combine

// UIKit에서는 ObservableObject 프로토콜을 채택 안해도 동작함.
// SwiftUI에서는 @StateObject 로 ViewModel 을 선언해야 하기 때문에 꼭 필요함.
// 예제에서는 그냥 멋있어 보이려고 채택함
class TimerClass: ObservableObject {
    
    // 프로퍼티에 @Published 프로퍼티 래퍼를 달아주면 값을 발행(publish) 할 수 있게 됨
    @Published var count: Int = 0
    var timer: Timer = Timer()
    
    init() {
        initTimer()
    }
    
    func initTimer() {
        //1초에 한번 publish 하는 값을 +1씩 갱신
        self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] _ in
            guard let self = self else { return }
            self.count += 1
        })
    }
    
    func stopTimer() {
        self.timer.invalidate()
    }
}

class CombineTimerExampleViewController: UIViewController {
    
    let timerViewModel: TimerClass = TimerClass()
    let timerCount: UILabel = UILabel()
    let stopButton: UIButton = UIButton()
    
    //Publisher에서 방출되는 값은 AnyCancellable 타입이기 때문에 AnyCancellable 타입인 cancellable 에 담은 후 방출
    var cancellable: AnyCancellable?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //뷰 셋업
        setupView()
        
        //뷰 바인딩
        bind()
    }
    
    private func bind() -> () {
        // 뷰에 갱신될 값은 발행자인 sink() 를 통해서 방출됨.
        // sink()가 방출하는 값은 AnyCancellable 타입이고, 이걸 위해 만들어둔 self.cancellable에 담고
        // sink()클로저에 어느 뷰와 묶어줄지만 선언해주면 알아서 값이 바뀔 때마다 뷰가 갱신됨.
        // 주의해야 할 점은 뷰 갱신을 위해 @Published 프로퍼티를 받아올 때에는 $수정자를 붙여야 함 (.count 가 아니고 .$count)
        self.cancellable = timerViewModel.$count.sink { [weak self] count in
            guard let self = self else { return }
            self.timerCount.text = "\(count)"
        }
    }
    
    private func setupView() -> () {
        timerCount.textAlignment = .center
        timerCount.font = UIFont.systemFont(ofSize: 30)
        timerCount.translatesAutoresizingMaskIntoConstraints = false
        
        stopButton.configuration = .borderedProminent()
        stopButton.configuration?.title = "Stop Timer"
        stopButton.addTarget(self, action: #selector(stopTimer), for: .touchUpInside)
        stopButton.translatesAutoresizingMaskIntoConstraints = false
        
        self.view.backgroundColor = UIColor.systemBackground
        self.view.addSubview(timerCount)
        self.view.addSubview(stopButton)
        
        NSLayoutConstraint.activate([
            timerCount.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
            timerCount.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
            timerCount.widthAnchor.constraint(equalTo: self.view.widthAnchor, constant: -40),
            timerCount.heightAnchor.constraint(equalToConstant: 40),
            
            stopButton.topAnchor.constraint(equalTo: timerCount.bottomAnchor, constant: 20),
            stopButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor)
        ])
    }
    
    @objc func stopTimer() {
        //타이머 멈추기
        timerViewModel.stopTimer()
    }
    
}

WRITTEN BY
artfrige
베이스 연주는 건강에 좋습니다
,

엑스코드에서 프로젝트 이름을 바꾸며 내부 디렉토리의 디렉토리 이름까지 전부 바꾸는 바람에 dependency 경로에 문제가 생겨서 프로젝트를 수동으로 옮길 뻔 했는데..
다행히 구글의 한 용자 덕분에 나의 시간과 노력을 아낌.

~/Library/Developer/Xcode/DerivedData/
  1. 문제가 있는 Dependency를 삭제한 후 엑스코드를 종료함
  2. 파인더에서 위의 경로로 이동한 다음 DerivedData 디렉토리 안의 문제가 있는 Dependency에 관련된 디렉토리를 지움
    (혹시 모르면 다 지우던가..)
  3. XCode 를 실행하고 다시 Dependency를 설치.
  4. PROFIT!

이래도 안되면? 나도몰루...


WRITTEN BY
artfrige
베이스 연주는 건강에 좋습니다
,

ㅈ바스크립트에서 await 은 Promise 를 방출하는 놈에 달아줄 수 있다

async function printData(){
    let endpoint = "https://jsonplaceholder.typicode.com/posts/1"
    let dataset = await fetch(endpoint).then((res) => { return res.json() })

    try {
        console.log(dataset)
    } catch(e) {
        console.log(e)
    }
}

printData() //쨘 json 데이터 프린트 성공

위 코드에서 fetch 가 Promise 를 방출하기 때문에 await 키워드를 달아서 파싱된 json을 dataset 에 담아 줌.
그렇기 때문에 try 문에서 받아온 json 데이터를 .then()으로 Promise 해결하지 않고도 사용 가능.

async/await 을 쓰는 이유야 여러개 있겠지만, 그 중 최고의 이유는 .then() 체인조차도 마지막엔 콜백지랄을 해야되기 때문에 어떻게든 불편할 수밖에 없었던 우리의 마음을 달래기 위한 최고의 솔루션이기 때문이지 않나 싶고...


WRITTEN BY
artfrige
베이스 연주는 건강에 좋습니다
,
$composer require vlucas/phpdotenv

을 프로젝트 루트 디렉토리에서 실행하면 의존성에 phpdotenv가 주입됨
그다음 index.php에서 아래 코드를 최상단에 추가함

require 'vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

그다음 프로젝트 root디렉토리에 .env 파일을 생성하고 아래와 같이 불러 쓰면됨

$someEnvVariable = $_ENV["YOUR_ENV_VARIABLENAME"]

composer 가 설치되지 않았거나 설치하지 않은 채로 적용하고 싶다면 아래 github repository를 참조할 것.
https://github.com/agungjk/phpdotenv-for-codeigniter


WRITTEN BY
artfrige
베이스 연주는 건강에 좋습니다
,

아 걱정하지 마십시오!
그런 당신을 위해 준비한

.navigationDestination(isPresented: Binding<Bool>, destination: () -> View)

가 있으니까요..
(그리고 당연히 위 메서드는 NavigationStack 안의 View객체 밑에 달아줘야 함..)


WRITTEN BY
artfrige
베이스 연주는 건강에 좋습니다
,

Baldur's Gate III

hobby 2023. 8. 14. 23:59

아 이걸 어떻게 참냐고 ㅋㅋㅋㅋㅋ
세일 언제하냐?


WRITTEN BY
artfrige
베이스 연주는 건강에 좋습니다
,

가령 아래와 같은 코드가 있다고 친다면,

@Binding var momentDate: Date

위 변수에는 세 가지 방법으로 접근할 수 있다.

  1. self._momentDate
  2. self.momentDate
  3. self.$momentDate

1번은 Binding<Date> 구조체 자체를 나타낸다.
2번은 self._momentDate.wrappedValue를 타나낸다. 즉 Date 자료형의 값 그 자체이다. View Body의 UI를 렌더링 할 때에는 이 값을 사용한다.
3번은 self._momentDate.projectedValued와 같다. 다시 말해 Binding<Date>이며, 이 값은 Child View에게 내려주면 Child View의 UI가 값을 변경할 때 사용할 수 있다.

Binding의 경우 Projected Value($)는 self 이다. 그리고 _와 $의 차이는 액세스 레벨 뿐이다. 하지만 프로퍼티 래퍼마다 각자 다른 형식의 값을 나타낼 수 있다. (예를 들어 Language Guide@SmallNumber를 살펴보자)

원문: StackOverflow


WRITTEN BY
artfrige
베이스 연주는 건강에 좋습니다
,
git config --global user.name 'YOUR_GITHUB_ACCOUNT'                               
git config --global user.password 'YOUR_GITHUB_ACCOUNT_TOKEN'

github account token은 아래의 경로에서 발급받을 수 있다.

1. 계정 > Settings


2. 좌측 사이드바 메뉴에 Developer Settings


Tokens 에서 발급받은 내용을 입력

 


WRITTEN BY
artfrige
베이스 연주는 건강에 좋습니다
,
 ! [rejected]        main -> main (fetch first)

위 에러가 나오고 push 가 안될때 강제로 해결하는 법.
주의할 점은 아래 방법을 수행할 시 리모트에 있는 모든 커밋이 증발하므로 여러 사람이 함께 작업하는 공동 프로젝트에는 사용하지 말 것.

git push -u origin +REMOTE_REPO_BRANCH

예) git push -u origin +main 

위의 명령을 실행하면 리모트 브랜치에 강제로 push 해서 덮어 써 버리게 된다.


WRITTEN BY
artfrige
베이스 연주는 건강에 좋습니다
,

지난번 밧심 텍스트 관제에 이어서 이번에는 한가한 공항을 골라 드디어 음성관제에 도전함. 지도를 둘러보다 보니 리스본 국제공항에 그라운드 관제사분이 들어와 있길래 냉큼 접속후 비행계획 제출.

근데 내가 자꾸 똥비행기(세스나 172)를 타고 다녀서 그런건지 만나는 관제사분들마다 "너 RNAV(GPS 항법장비)되니?" 라고 물어봐서 좀 쪽팔림. 내가 타는 세스나에는 Garmin 530 내비게이션이 달려있어서 RNAV 비행은 충분히 가능하지만 아닌 항공기도 있으니 이해는 하지만 이제 세스나 그만 타고 좋은비행기로 갈아타야되나 싶고...

오늘의 비행계획
오늘의 비행경로

오늘의 비행계획은 리스본 국제공항 RWY03으로 이륙후 INBOM4 departure를 타고 INBOM - ABLEG 이후에 포르투 국제공항에 착륙하는 것. 비행시간은 약 1시간 30분정도고 거리는 150nm(약 280km) 정도. 다행히 제출한 비행계획에서 수정 없이 허가가 나왔고 순항고도는 3000ft를 받게 되었다. 

야간비행이라서 바깥에는 불빛 외엔 아무것도 안보이고

이륙 후 순항고도까지는 무사히 올라왔는데 이놈의 오토파일럿이 버그인지 뭔지 중간중간 엉뚱한데로 가려고 해서 신경이 많이 쓰였음. 레이더 벡터 받는것처럼 가는 등 임기응변으로 어떻게 잘 가긴 했지만... 역시 좋은비행기로 갈아타야겠다.

마지막 웨이포인트에서 공항까지 접근하는 루트를 선택할 때 원래는 RWY35로 내리려고 했지만 공항 ATIS(기상정보)를 들어보니 RWY17로 내린다고 해서 루트를 급 변경함

원래 계획이었던 ABLEG 5C Arrival
하지만 바람 방향이 바뀌어서 반대편인 RWY17로 변경. 하지만 이쪽엔 ILS가 있어서 활주로까지 정밀유도를 받을 수 있다
ILS 정렬 완료되었고 활주로도 아주 잘 보이는구나
무사히 착륙 후 주기장까지 잘 왔다고 합니다
죽지않고 살아서 잘 내렸다고 한다.

이렇게 첫 밧심 음성관제 비행을 잘 마쳤다.
다음번엔 타워랑 컨트롤러까지 있는데서 좀더 많은 관제서비스를 받아보는게 목표.

끝!


WRITTEN BY
artfrige
베이스 연주는 건강에 좋습니다
,