2019년 12월 12일 목요일

2019년 12월 7일 토요일

파이어족이 온다. 스콧 리킨스 책 독서 후기입니다.

FIRE(Financial, Independence, Retire, Early)족이 온다. 제목이 너무 흥미로워서 교보문고에서 잠시 보고 바로 구매했습니다. 제가 정말 좋아하는 미니멀리스트의 삶과도 닮은 부분이 있습니다. 

금융위기 후 전 세계 젊은이들을 사로잡은 라이프스타일 혁명, 30대에 경제적 자유를 이루고 꿈꾸던 삶을 사는 사람들이 부제입니다. 많은 사람들이 경제적 자유를 이루고 싶어 합니다. 

필사한 내용입니다. 
파이어는 부자만 할 수 있을까?
내가 본 파이어를 실천하는 사람 중에서 기술 직종에 일하는 사람들의 소득은 대부분 20만 달러(2억 2,000만원)수준이었다. 하지만 총 소득이 7만 달러(7,700만원)인 가족을 만난 적도 있다. 독신자도 만났고 아이가 네다섯인 가족과도 얘기를 나눴다. 당신이 무엇을 가졌든, 어떤 사람이든, 얼마를 벌든 간에 마음의 평화를 얻게 하는 것이 바로 파이어가 가는 길이다.

직장으로 출근하는 고속도로에서 차들이 내 앞에 일렬로 멈춰 서 있을 때 나는 가장 좋아하는 팟캐스트인 팀 페리스 쇼를 듣고 있었다. 그의 팟캐스트 소개글에는 "당신이 활용할 수 있는 전략, 도구, 방법을 얻고자 투자, 스포츠, 경제, 예술 등 다양한 분야에서 최고 수준에 올라선 사람들을 분석한다"라고 쓰여 있다. 
그의 지난 팟캐스트 목록 중에서 "미스터 머니 머스태시"가 1년에 2만 5,000 ~ 2만 7,000달러로 아름답게 사는 방법이라는 이상한 제목을 보고 호기심이 생겨서 재생 버튼을 눌렀다. 미스터 머니 머스태시의 본명은 피트 아데니로 캐나다에서 태어나 서른 살에 은퇴한 평범한 엔지니어였다. 그는 콜로라도 주의 볼더에서 가족과 함께 살면서 2005년 이후로 "진짜" 직업을 가져본 적이 없었다. 페리스가 팟캐스트에서 그를 소개하면서 질문을 던졌다. "이 가족이 잘 사는 비결이 뭘까요? 여러 가지가 있겠지만, 사실은 가장 적은 비용으로 인생을 최대한 즐기며 살 수 있도록 다양한 방식으로 생활을 최적화했고, 인덱스 펀드와 부동산 투자를 활용했기 때문에 성공적으로 조기 은퇴를 할 수 있었습니다. 이 가족은 연간 2만5,000~2만7,000달러를 쓰지만 전혀 부족함을 느끼지 않습니다." 


41퍼센트의 법칙 
파이어 공식에 따르면 연 생활비 지출의 25배를 저축하면 은퇴 준비가 가능하다. 어떻게 그것이 가능할까? 
예를 들어 당신이 생활비로 연간 5만 달러(5,500만원)을 쓴다고 가정해보자. 은퇴를 준비하려면 125만 달러(13억 7,500만원)을 저축해야 한다. 연 5퍼센트의 수익이 난다고 가정해보자. 125만 달러의 5퍼센트는 6만2,500달러(6,875만원)이다. 물론 이는 매우 보수적으로 가정해서 계산한 것이며 대개는 수익률이 더 높을 것이라 생각한다. 그러므로 매년 투자 수익의 4퍼센트 혹은 5만 달러만 인출한다면 경기 인플레이션과 시장 하락에 항상 대비할 수 있다. "안전한 인출률"이라고도 부르는 "4퍼센트 법칙"은 트리니티 대학의 연구를 기초로 하고 있으며, 은퇴자가 원금을 축내거나 훼손하지 않는 범위에서 매년 인출할 수 있는 달러 금액을 결정할 때 사용한다. 

파이어를 주제로 다룬 인기 블로그
미스터 머니 머스태시: mrmoneymustache.com
가장 쉽고 빠르게 부자로 가는 길: jlcollinsnh.com

자신을 행복하게 하는 중요한 10가지
1. 내 아이가 웃는 소리 듣기
2. 남편과 커피 마시기
3. 아이를 꼭 안아주기
4. 산책하기
5. 자전거 타기
6. 와인 한 잔 즐기기
7. 질 좋은 초컬릿
8. 부모님을 비롯한 가족과 대화하기
9. 가족끼리 저녁 식사하기
10. 아이에게 책 읽어주기

은퇴 계산기는 어떻게 사용하는가? 
은퇴 계산기는 저축률을 산출하기 위해 현재 수입에서 연간 지출 금액을 차감한 후 계산한다. 저축률을 활용해서 투자에서 얻은 연간 투자 수익으로 당신이 생활비를 충당하려면 몇 년 동안 일해서 투자금액을 모아야 하는지를 계산한다. 
아래의 사이트에서 가능하다.

리킨스 가족의 월평균 지출 내역 
우리집  지출 금액은 한달 평균 1만340달러(1,137만원)으로 1년으로 환산하면 12만4,080달러(1억 3,648만원)였다. 우리 수입이 좋을지도 모르지만 사실상 여전히 버는 대로 전부 쓰면서 살았다.

10만 달러(1억 1,000만원)으로 시작한 인덱스 펀드에 40년 동안 0.05퍼센트 수수료를 내면서 기대 수익률을 8퍼센트로 투자했을 때는 213만 달러(23억 4,300만원)이 생긴다. 그러나 같은 금액을 동일한 조건으로 수수료 1퍼센트를 받는 금융 자산관리인을 고용한다고 가정했을 때 마지막에 손에 들어오는 돈은 140만 달러(15억 4,000만원)이다. 인덱스 펀드가 해주는 것과 크게 다를 바 없는 일을 하는 사람에게 73만 달러(8억 300만원)을 쥐어주는 것이나 마찬가지다.

성공의 비결이 뭐냐고 묻자마자 카슨은 바로 대답했다. "근검절약, 끈기, 배움에 대한 갈망입니다" 카슨은 언제나 번 것보다 훨씬 적은 비용으로 생활했다. 소득이 6자리가 훨씬 넘고 1년에 50건이 넘는 부동산 계약을 성사시키는 지금도 여전히 낡은 도요타 자동차를 만족해하며 타고 있다. 좋은 매물이 언제 나타날지 몰라 늘 현금을 챙겨 뒀고, 그런 근검 절약하는 생활습관 덕분에 2008년 경기 침체기를 버틸 수 있었다. 

"버는 것보다 적게 쓰고 남는 돈을 투자하라"
같은 여정을 하는 사람을 만나는 것이 매우 중요하다는 것 역시 깨달았다. 파이어의 목표는 돈을 모아서 경제적 자유를 누리는 것이지만 훨씬 차분하고 신중하게 자기주도적으로 사는 인생도 마찬가지로 중요하며, 내가 만난 전문가들은 모두 이 사실을 구체화하고 있었다. 
"무엇이든 살 수 있지만 또한 모든 것을 살 수는 없다"

비용 절감의 기본 원칙: 매드 파이언티스트가 해주는 충고
1. 버는 돈보다 적게 써라
사람들은 때때로 가장 중요한 "얼마만큼 저축하는가"를 놓친다. 저축을 하는 한 성과를 내고 있는 것이 맞다. 파이어에서는 높은 비율이나 가속율로 저축을 하는 것이 핵심이다. 
2. 차액을 투자하자
기본적으로 파이어는 돈을 목적으로 일하는 것이 아니라 당신을 위해 돈이 일하게 만드는 것이 핵심이다. 인플레이션이 은행에서 지급하는 이자율보다 더 높은 비율로 상승하기 때문이다. 같은 금액을 주식 시장에 투자한다면 가치는 증가한다. 

머스태시족이란 과연 무엇인가?
공식적으로 미스터 머니 머스태시로 알려진 피트 아데니가 쓴 글을 따르는 사람이라면 누그든 머스태시족이다. 아데니가 쓴 글에서 제시한 지침에 따라 연간 비용을 4만달러(4,400만원)미만으로 유지하는 것을 목표로 하는 매우 검소한 사람들이다. 그들은 연비가 좋은 차를 사거나 아예 차를 포기하고, 겨울에도 난방비를 줄이려고 온도를 낮게 유지하며 모든 방면에서 비용을 최소화하려고 노력한다. 머스태시족은 주류 문화가 이끄는 것에 따르는 것이 아니라 스스로 가치에 맞는 의사결정을 내린다. 

16퍼센트 수준의 저축률을 유지한다면
34년 안에 은퇴할 수 있다.
12만 달러(1억 3,200만원)을 매년 지출하고
2만 2,000달러(2,420만원)을 저축하며
1만 달러(1,100만원)가 월별 지출액이고
1,833달러(201만6,300원)을 매월 저축한다.

58퍼센트 수준의 저축률을 유지한다면
10년 안에 은퇴할 수 있다. 
6만 달러(6,600만원)을 매년 지출하고
8만2,00달러(9,020만원)을 저축하며
5,000달러(550만원)가 월별 지출액이고
6,833달러(751만6,300원)을 매월 저축한다.

매우 재미있는 책입니다. 젊은 나이에 조기 은퇴를 꿈꾸는 파이어족을 꿈꾼다면 한번 읽어볼만한 책입니다. ^^

2019년 11월 15일 금요일

오늘도 아침 산책

고덕, 명일, 상일동을 돌아보면 10년차가 넘어간 롯데캐슬이 가장 단풍이 아름답습니다.
아침에 산책하면서 촬영한 사진들입니다.








2019년 11월 11일 월요일

제법 날씨가 쌀쌀합니다. 아침 산책하면서 촬영한 사진들입니다.

2019년이 벌써 가고 있습니다. 이제 2020년을 기다리는 시점에 와 있습니다.
올해는 반성하고 내년을 계획하고 있습니다. 상일동, 고덕동, 올림픽 공원에서 촬영한 사진들입니다.















2019년 11월 3일 일요일

주말에 산책하고 라이딩하면서 촬영한 사진들입니다.

일년이 정말 빠르게 지나간 것 같습니다. 2019년을 뒤로 하고 2020년이 다가옵니다.
한해 한해 열매를 맺고 열심히 살려고 하고 있는데 참 쉽지 않네요. ㅎㅎ
오랜만에 올림픽공원 산책을 다녀왔습니다. 올림픽공원과 서울숲은 봄과 가을에 정말 좋은 곳입니다. 느릿하게 산책을 하고 여유를 부리는 것이 참 좋습니다.








2019년 10월 12일 토요일

산책하기에 정말 좋은 계절입니다.

아침저녁으로 산책하기에 정말 좋은 날씨입니다. 휴일에는 아침에 1:30분정도 강아지 데리고 산책을 다닙니다. 고덕동, 상일동에 새로 입주한 아파트들이 많아서 내부 단지들을 돌고 있습니다. 그라시움만 4번정도 돌아보았습니다.
날씨도 좋고 걷기에도 무척 좋은 날씨 입니다. ㅎㅎ








2019년 10월 7일 월요일

iOS 13.*에 추가된 SwiftUI 연습 사용자 입력 추가하기 #3

세번째 데모는 입력 처리 추가하기 입니다. 아래의 링크에서 StartingPoing폴더에 있는 프로젝트를 오픈하면 됩니다.
사용자가 랜드마크를 보고 별표 표시를 하는 경우
스크린샷 2019-10-07 오후 4.18.52.png
StartingPoing폴더에 있는 시작 프로젝트를 오픈 합니다.
LandmarkRow파일에서 아래와 같이 수정합니다.
import SwiftUI
struct LandmarkRow: View {
var landmark: Landmark
var body: some View {
    HStack {
        landmark.image
            .resizable()
            .frame(width: 50, height: 50)
        Text(landmark.name)
        Spacer()
        
        if landmark.isFavorite {
            Image(systemName: "star.fill")
                .imageScale(.medium)
        }
    }
}
}
Section 2: List View를 필터링하기
사용자가 좋아하는 것만 필터링할 수 있습니다.
View에 상태를 추가하기 위해 @State속성을 사용할 수 있습니다.
LandmarkList를 아래와 같이 수정합니다.
import SwiftUI
struct LandmarkList: View {
@State var showFavoritesOnly = false

var body: some View {
    NavigationView {
        List(landmarkData) { landmark in
            if !self.showFavoritesOnly ||
                landmark.isFavorite {
                NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
                    LandmarkRow(landmark: landmark)
                }
            }
        }
        .navigationBarTitle(Text("Landmarks"))
    }
}
}
Section 3: 상태를 토글하기 위해 컨트롤을 추가합니다.
유저가 리스트 필터링을 컨트롤하도록, 컨트롤을 추가합니다.
LandmarkList를 수정합니다.
import SwiftUI
struct LandmarkList: View {
@State var showFavoritesOnly = true

var body: some View {
    NavigationView {
        List {
            ForEach(landmarkData) { landmark in
                if !self.showFavoritesOnly ||
                    landmark.isFavorite {
                    NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
                        LandmarkRow(landmark: landmark)
                    }
                }
            }
        }
        .navigationBarTitle(Text("Landmarks"))
    }
}
}
다시 아래와 같이 추가합니다.
import SwiftUI
struct LandmarkList: View {
@State var showFavoritesOnly = true

var body: some View {
    NavigationView {
        List {
            Toggle(isOn: $showFavoritesOnly) {
                Text("Favorites only")
            }
            
            ForEach(landmarkData) { landmark in
                if !self.showFavoritesOnly ||
                    landmark.isFavorite {
                    NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
                        LandmarkRow(landmark: landmark)
                    }
                }
            }
        }
        .navigationBarTitle(Text("Landmarks"))
    }
}
}
스크린샷 2019-10-07 오후 4.11.38.png
Section 4: Storage를 위해 Observable Object를 사용한다.
좋아하는 랜드마크를 컨트롤하도록 준비하기 위해 먼저 observable object로
랜드마크 데이터를 저장해야 합니다.
observable object는 storage로 부터 SwiftUI환경으로 뷰를 바운드할 수 있습니다.
SwiftUI는 뷰에 영향을 주는 observable object에 대한 변경을 감시하고,
그리고 변경후에 뷰의 올바른 버전을 표시합니다.
Models폴더에 UserData.swift파일을 추가해서 아래와 같이 코딩 합니다.
import SwiftUI
import Combine
final class UserData : ObservableObject {
@Published var showFavoritesOnly = false
@Published var landmarks = landmarkData
}
Section 5: 뷰에 Model Object을 붙이기
UserData 오브젝트를 생성한 후에 앱의 데이터 저장으로 뷰를 적용하도록 업데이트합니다.
LandmarkList를 수정합니다.
import SwiftUI
struct LandmarkList: View {
//다음과 같이 수정한다.
@EnvironmentObject var userData: UserData

var body: some View {
    NavigationView {
        List {
            Toggle(isOn: $userData.showFavoritesOnly) {
                Text("Favorites only")
            }
            
            ForEach(userData.landmarkData) { landmark in
                if !self.userData.showFavoritesOnly ||
                    landmark.isFavorite {
                    NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
                        LandmarkRow(landmark: landmark)
                    }
                }
            }
        }
        .navigationBarTitle(Text("Landmarks"))
    }
}
}
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
LandmarkList()
.environmentObject(UserData())
}
}
SceneDelegate도 수정합니다.
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

    // Use a UIHostingController as window root view controller
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(
            rootView: LandmarkList()
                .environmentObject(UserData())
        )
        self.window = window
        window.makeKeyAndVisible()
    }
}
LandmarkDetail을 수정합니다.
import SwiftUI
struct LandmarkDetail: View {
@EnvironmentObject var userData: UserData
var landmark: Landmark
var landmarkIndex: Int {
    userDAta.landmarks.firstIndex(
        where: { $0.id == landmark.id })!
}

var body: some View {
    VStack {
        MapView(coordinate: landmark.locationCoordinate)
            .frame(height: 300)

        CircleImage(image: landmark.image)
            .offset(x: 0, y: -130)
            .padding(.bottom, -130)

        VStack(alignment: .leading) {
            Text(landmark.name)
                .font(.title)

            HStack(alignment: .top) {
                Text(landmark.park)
                    .font(.subheadline)
                Spacer()
                Text(landmark.state)
                    .font(.subheadline)
            }
        }
        .padding()

        Spacer()
    }
    .navigationBarTitle(Text(verbatim: landmark.name), displayMode: .inline)
}
}
struct LandmarkDetail_Previews: PreviewProvider {
static var previews: some View {
LandmarkDetail(landmark: landmarkData[0])
.environmentObject(UserData())
}
}
Section 6: 각 랜드마크에 대한 Favorite 버튼을 생성합니다.
LandmarkDetail을 수정합니다.
import SwiftUI
struct LandmarkDetail: View {
@EnvironmentObject var userData: UserData
var landmark: Landmark
var landmarkIndex: Int {
    userData.landmarks.firstIndex(where: { $0.id == landmark.id })!
}

var body: some View {
    VStack {
        MapView(coordinate: landmark.locationCoordinate)
            .edgesIgnoringSafeArea(.top)
            .frame(height: 300)
        
        CircleImage(image: landmark.image)
            .offset(x: 0, y: -130)
            .padding(.bottom, -130)
        
        VStack(alignment: .leading) {
            HStack {
                Text(verbatim: landmark.name)
                    .font(.title)
                
                Button(action: {
                    self.userData.landmarks[self.landmarkIndex]
                        .isFavorite.toggle()
                }) {
                    if self.userData.landmarks[self.landmarkIndex]
                        .isFavorite {
                        Image(systemName: "star.fill")
                            .foregroundColor(Color.yellow)
                    } else {
                        Image(systemName: "star")
                            .foregroundColor(Color.gray)
                    }
                }
            }
            
            HStack(alignment: .top) {
                Text(verbatim: landmark.park)
                    .font(.subheadline)
                Spacer()
                Text(verbatim: landmark.state)
                    .font(.subheadline)
            }
        }
        .padding()
        
        Spacer()
    }
}
}
struct LandmarkDetail_Preview: PreviewProvider {
static var previews: some View {
let userData = UserData()
return LandmarkDetail(landmark: userData.landmarks[0])
.environmentObject(userData)
}
}
실행하면 다음과 같습니다.

스크린샷 2019-10-07 오후 4.46.32.png
스크린샷 2019-10-07 오후 4.46.40.png

2019년 10월 6일 일요일

iOS 13.* 에 추가된 SwiftUI 두번째 데모 네비게이션과 리스트 출력입니다 #2

두번째로 리스트와 네비게이션 사용하기 입니다.
아래의 예제가 상당히 재미있습니다.
기존에 스토리보드를 많이 사용하던 개발자라면 한번 테스트해 보세요.
아래의 링크에서 샘플을 받아서 Starting폴더에 있는 프로젝트를 오픈해야 합니다.
새로운 파일들이 많이 추가되어 있습니다. 새로 제공된 시작 프로젝트를 오픈 합니다.
Section 2: Row View 생성하기
스크린샷 2019-10-07 오후 3.08.41.png
각 랜드마크의 상세를 표시하는 row를 생성합니다.
SwiftUI템플릿을 선택해서 LandmarkRow.swift를 추가합니다.
아래와 같이 입력합니다.
import SwiftUI
struct LandmarkRow: View {
var body: some View {
HStack {
landmark.image
.resizable()
.frame(width: 50, height: 50)
Text(landmark.name)
}
}
}
struct LandmarkRow_Previews: PreviewProvider {
static var previews: some View {
LandmarkRow(landmark: landmarkData[0])
}
}
Section 3: Row Preview를 커스터마이징하기
Xcode의 canvas는 자동으로 현재 에디터의 어떤 형식도 감지하고 표시합니다.
Preview provider는 하나 또는 그 이상의 뷰들을 리턴 합니다. 사이즈와 디바이스로 셋팅된 옵션과 함께 합니다.
import SwiftUI
struct LandmarkRow: View {
var body: some View {
HStack {
landmark.image
.resizable()
.frame(width: 50, height: 50)
Text(landmark.name)
}
}
}
struct LandmarkRow_Previews: PreviewProvider {
static var previews: some View {
Group {
LandmarkRow(landmark: landmarkData[0])
.previewLayout(.fixed(width:300, height: 70))
LandmarkRow(landmark: landmarkData[1])
.previewLayout(.fixed(width:300, height: 70))
}
.previewLayout(.fixed(width:300, height: 70))
}
}
LandmarkList라는 SwiftUI템플릿을 사용한 파일을 추가합니다. 아래와 같이 수정합니다.
import SwiftUI
struct LandmarkList: View {
var body: some View {
List {
LandmarkRow(landmarK: landmarkData[0])
LandmarkRow(landmarK: landmarkData[1])
}
}
}
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
LandmarkList()
}
}
Section 5: 동적인 리스트 만들기
컬렉션에서 직접 로우를 생성할 수 있습니다.
아래와 같이 LandmarkList를 수정합니다.
import SwiftUI
struct LandmarkList: View {
var body: some View {
List(landmarkData, id: .id) { landmark in
LandmarkRow(landmark: landmark)
}
}
}
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
LandmarkList()
}
}
Models폴더에 있는 Landmark.swift를 살펴봅니다.
아래와 같이 id:을 삭제하고 코드를 수정합니다.
import SwiftUI
struct LandmarkList: View {
var body: some View {
List(landmarkData) { landmark in
LandmarkRow(landmark: landmark)
}
}
}
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
LandmarkList()
}
}
실행하면 아직은 상세화면만 출력됩니다.
스크린샷 2019-10-07 오전 11.52.40.png
Section 6: 리스트와 상세 사이에 네비게이션
import SwiftUI
struct LandmarkList: View {
var body: some View {
NavigationView {
List(landmarkData) { landmark in
NavigationLink(destination: LandmarkDetail()) {
LandmarkRow(landmark: landmark)
}
}
.navigationBarTitle(Text("Landmarks"))
}
}
}
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
LandmarkList()
}
}
Section 7: Child View로 데이터 전달하기
LandmarkDetail 뷰는 상세 정보가 하드 코딩 되어 있습니다.
데이터를 넘겨 받도록 합니다.
LandMarkDetail을 아래와 같이 수정합니다.
import SwiftUI
struct LandmarkDetail: View {
var landmark: Landmark
var body: some View {
    VStack {
        MapView(coordinate: landmark.locationCoordinate)
            .frame(height: 300)

        CircleImage(image: landmark.image)
            .offset(x: 0, y: -130)
            .padding(.bottom, -130)

        VStack(alignment: .leading) {
            Text(landmark.name)
                .font(.title)

            HStack(alignment: .top) {
                Text(landmark.park)
                    .font(.subheadline)
                Spacer()
                Text(landmark.state)
                    .font(.subheadline)
            }
        }
        .padding()

        Spacer()
    }
    .navigationBarTitle(Text(verbatim: landmark.name), displayMode: .inline)
}
}
struct LandmarkDetail_Previews: PreviewProvider {
static var previews: some View {
LandmarkDetail(landmark: landmarkData[0])
}
}
LandmarkRow는 아래와 같이 수정합니다.
import SwiftUI
struct LandmarkRow: View {
var landmark: Landmark
var body: some View {
    HStack {
        landmark.image
            .resizable()
            .frame(width: 50, height: 50)
        Text(verbatim: landmark.name)
        Spacer()
    }
}
}
struct LandmarkRow_Previews: PreviewProvider {
static var previews: some View {
Group {
LandmarkRow(landmark: landmarkData[0])
LandmarkRow(landmark: landmarkData[1])
}
.previewLayout(.fixed(width: 300, height: 70))
}
}
Models폴더에 있는 Landmark는 프로토콜만 추가합니다.
import SwiftUI
import CoreLocation
struct Landmark: Hashable, Codable, Identifiable {
var id: Int
var name: String
fileprivate var imageName: String
fileprivate var coordinates: Coordinates
var state: String
var park: String
var category: Category
LandmarkList는 아래와 같이 수정합니다.
import SwiftUI
struct LandmarkList: View {
var body: some View {
NavigationView {
List(landmarkData) { landmark in
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
.navigationBarTitle(Text("Landmarks"))
}
}
}
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
ForEach(["iPhone SE", "iPhone XS Max"], id: .self) { deviceName in
LandmarkList()
.previewDevice(PreviewDevice(rawValue: deviceName))
.previewDisplayName(deviceName)
}
}
}
진입점 역할을 수행하는 코드는 SceneDelegate에 있습니다.
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

    // Use a UIHostingController as window root view controller
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: LandmarkList())
        self.window = window
        window.makeKeyAndVisible()
    }
}
실행하면 다음과 같이 네비게이션이 되고 상세 화면으로 전환할 수 있습니다.

스크린샷 2019-10-07 오후 3.00.29.png
스크린샷 2019-10-07 오후 3.00.36.png

iOS 13.* Xcode 11.1에서 작업하는 SwiftUI에 대한 데모 입니다. #1

다른 코드들은 큰 변화가 없는데 SwiftUI가 상당히 좋아 보입니다. 아직은 사용하려면 연습이 필요하고 익숙해지는 시간이 필요하지만 꽤 잘 만들어져있습니다.
선언적인 UI구성과 미리보기등이 가능합니다.
스크린샷 2019-10-07 오전 11.09.13.png
스크린샷 2019-10-07 오전 11.10.59.png
이런 앱을 직접 만들어볼 수 있는 튜토리얼 사이트가 무척 재미있게 제공됩니다.
Landmarks라는 싱글뷰 앱을 SwiftUI기반으로 생성합니다. 미리 리소스를 다운로드하면 됩니다.
기본적으로 추가된 ContentView에는 2개의 구조체가 있습니다. 미리보기는 macOS 10.15에서 지원됩니다.
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
그냥 실행해서 결과를 보면 Hello World가 출력됩니다.
아래와 같이 추가해서 텍스트뷰를 커스터마이징합니다.
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Turtle Rock")
.font(.title)
.foregroundColor(.green)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
스택뷰를 통해 쌓기를 합니다.
struct ContentView: View {
var body: some View {
VStack {
Text("Turtle Rock")
.font(.title)
Text("Joshua Tree National Park")
}
}
}
스크린샷 2019-10-07 오전 11.26.01.png
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
}
    }
        
}
}
스크린샷 2019-10-07 오전 11.31.49.png
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
}
    }
    .padding() 약간의 여유를 위해 추가한다. 
}
}
이미지 커스터마이징하기
다운로드 받은 turtlerock.jpg를 에셋에 추가합니다. 
프로젝트에 새로운 파일을 추가하면서 Swift UI템플릿을 사용합니다. CircleImage라는 이름으로 추가합니다. 
스크린샷 2019-10-07 오전 11.38.30.png
아래와 같이 코드를 수정합니다. 
import SwiftUI
struct CircleImage: View {
var body: some View {
Image("turtlerock")
.clipShape(Circle())
.overlay(
Circle().stroke(Color.gray, lineWidth: 4))
.shadow(radius: 10)
}
}
struct CircleImage_Previews: PreviewProvider {
static var previews: some View {
CircleImage()
}
}
UIKit과 SwiftUI를 같이 사용할 수 있습니다.
새로운 파일을 추가하면서 Swift UI템플릿을 선택합니다. MapView라는 이름을 입력합니다.
아래와 같이 코드가 추가됩니다. 생성된 코드를 아래와 같이 수정합니다.
import MapKit
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
    let coordinate = CLLocationCoordinate2D(
        latitude: 34.011286, longitude: -116.166868)
    let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
    let region = MKCoordinateRegion(center: coordinate, span: span)
    view.setRegion(region, animated: true)
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView()
}
}
Section 6: 상세뷰를 조합하기
ContentView파일을 아래와 같이 수정하면 병합된 뷰를 볼 수 있습니다.
//
// ContentView.swift
// Landmarks
//
// Created by JONG DEOK KIM on 07/10/2019.
// Copyright © 2019 multicampus. All rights reserved.
//
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
MapView()
.frame(height:300)
        CircleImage()
            .offset(y: -130)
            .padding(.bottom, -130)
        
        VStack(alignment: .leading) {
            Text("Turtle Rock")
                .font(.title)
            HStack {
                Text("Joshua Tree National Park")
                    .font(.subheadline)
                Spacer()
                Text("California")
                    .font(.subheadline)
            }
        }
        .padding()
            
        Spacer()
    }
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
스크린샷 2019-10-07 오전 11.52.40.png
아직은 저도 연습을 하고 있는데 재미있는 기술입니다. ^^

요즘 많이 들리는 RAG에 대한 멋진 정리가 있어서 공유합니다. ㅎㅎ

 작년에는 ChatGPT가 크게 유행을 했는데 올해는 Gen AI, LLM, 랭체인등이 유행하고 있습니다. ㅎㅎ  RAG라는 단어도 상당히 많이 들리고 있습니다. 멋진 정리의 링크입니다.  https://brunch.co.kr/@ywkim36/146?...