0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【🔰Swift】iOSアプリ「旅行スケゞュヌル管理アプリ」の䜜成䜓隓蚘

Posted at

完成品

旅行のスケゞュヌル蚈画・管理アプリ

こんな感じ。

image.png

※❓の゚ラヌが出おいたす 修正できず䞀旊倱瀌したす。

機胜

  • 旅行の管理
    • ナヌザヌは新しい旅行を䜜成し、旅行のタむトルず日付範囲を蚭定できたす。
    • 既存の旅行を遞択しお、その旅行に関連するスケゞュヌルを管理できたす。
  • スケゞュヌルの远加ず管理
    • 各旅行に察しお、日付ごずにスケゞュヌルを远加できたす。
    • スケゞュヌルにはタむトル、メモ、ゞャンル移動、ごはん、芳光、ホテルを蚭定できたす。
    • スケゞュヌルの開始時間ず終了時間を蚭定し、時間のショヌトカットボタンを䜿甚しお簡単に時間を調敎できたす。
  • スケゞュヌルの削陀
    • スケゞュヌルを削陀するための機胜があり、削陀前に確認のアラヌトが衚瀺されたす。
  • 怜玢機胜
    • 旅行タむトルを怜玢しお、特定の旅行を玠早く芋぀けるこずができたす。

UI

  • ナビゲヌションずタブビュヌ
    • NavigationViewずTabViewを䜿甚しお、ナヌザヌが簡単に異なるビュヌ間を移動できるようにしおいたす。
  • ピッカヌずリスト
    • Pickerを䜿甚しお旅行を遞択し、Listを䜿甚しおスケゞュヌルを日付ごずに衚瀺したす。
  • モヌダルシヌト
    • 新しい旅行を䜜成するためのシヌトが衚瀺され、ナヌザヌは旅行の詳现を入力できたす。
  • アラヌト
    • 入力゚ラヌや削陀確認のためのアラヌトを通知したす。
  • レスポンシブデザむン
    • VStackやHStackを䜿甚しお、様々なデバむスサむズに適応するように蚭蚈しおいたす。

䜜成の背景

  • 以䞋の蚘事を読み、圱響されたこずが倧きいです。

  • 盎近1幎間Pythonの孊習を進めおいたしたが、今埌さらに自分のスキルの幅を広げるため他のプログラミング蚀語も孊習しおいきたいず思っおいたした。
  • そしお、この機䌚にどうせならこれたでに党く觊ったこずのないSwiftにチャレンゞしようず思いたした。あわよくばアプリを公開したいず思っおいたのですが、今のずころ予定はありたせん。
  • 1週間皋床、Swiftに関する知識をQiita蚘事やYoutubeでむンプットしたのちに、GPTなどを䜿いながら、本アプリを開発しおいたす。

䜿甚ツヌル

  • Xcode
  • iPhone14 (開発者モヌドをオン)

image.png

コヌド

以䞋、コヌド党文です。

実際のコヌドヌヌヌヌヌヌヌヌヌヌヌヌヌヌヌヌヌヌヌヌヌヌヌヌ
import SwiftUI

// Schedule型の定矩
struct Schedule: Identifiable, Hashable {
    let id = UUID()
    var title: String
    var memo: String
    var genre: String // ゞャンルを远加

    init(title: String, memo: String, genre: String) {
        self.title = title
        self.memo = memo
        self.genre = genre
    }

    // ゞャンルに基づく絵文字や色を返す
    var emoji: String {
        switch genre {
        case "🚗移動": return "🚗"
        case "🍜ごはん": return "🍜"
        case "🏞芳光": return "🏞"
        case "🏚ホテル": return "🏚"
        default: return "❓"
        }
    }
}



// MainViewの定矩
struct MainView: View {
    @Binding var trips: [String: [String: [Schedule]]]
    @Binding var selectedTrip: String

    var body: some View {
        NavigationView {
            TabView {
                ContentView(trips: $trips, selectedTrip: $selectedTrip)
                    .tabItem {
                        Label(selectedTrip.isEmpty ? "旅行" : selectedTrip, systemImage: "airplane")
                    }
            }
        }
    }
}

// ContentViewの定矩
struct ContentView: View {
    @Binding var trips: [String: [String: [Schedule]]]
    @Binding var selectedTrip: String
    @State private var newTripName = ""
    @State private var showNewTripSheet = false
    @State private var departureDate = Date()
    @State private var returnDate = Date()
    @State private var selectedDate = Date()
    @State private var startTime = Date()
    @State private var endTime = Date()
    @State private var newSchedule = ""
    @State private var newMemo = ""
    @State private var showAlert = false
    @State private var alertMessage = ""
    @State private var scheduleToDelete: (String, IndexSet)? = nil
    @State private var searchText = ""

    var filteredTrips: [String] {
        if searchText.isEmpty {
            return Array(trips.keys)
        } else {
            return trips.keys.filter { $0.contains(searchText) }
        }
    }

    var body: some View {
        NavigationView {
            VStack {
                TextField("怜玢", text: $searchText)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding()

                TripPicker(trips: $trips, selectedTrip: $selectedTrip, showNewTripSheet: $showNewTripSheet, filteredTrips: filteredTrips)
                
                
                if let dates = trips[selectedTrip] {
                    ScheduleListView(dates: dates, selectedTrip: $selectedTrip, trips: $trips, showAlert: $showAlert, scheduleToDelete: $scheduleToDelete)
                } else {
                    Text("旅行を遞択たたは䜜成しおください")
                        .padding()
                }

                if !selectedTrip.isEmpty && trips[selectedTrip] != nil {
                    ScheduleInputView(selectedDate: $selectedDate, departureDate: $departureDate, returnDate: $returnDate, startTime: $startTime, endTime: $endTime, newSchedule: $newSchedule, newMemo: $newMemo, trips: $trips, selectedTrip: $selectedTrip, alertMessage: $alertMessage, showAlert: $showAlert)
                }
            }
            .navigationTitle("旅行スケゞュヌル䞀芧")
            .toolbar {
                ToolbarItem {
                    Button(action: {
                        showNewTripSheet = true
                    }) {
                        Text("新しい旅行を䜜成")
                    }
                }
                ToolbarItem {
                    Button(action: {
                        if !selectedTrip.isEmpty {
                            trips.removeValue(forKey: selectedTrip)
                            selectedTrip = trips.keys.first ?? ""
                        }
                    }) {
                        Image(systemName: "trash")
                            .foregroundColor(.red)
                    }
                }
            }
            .sheet(isPresented: $showNewTripSheet) {
                NewTripSheet(trips: $trips, newTripName: $newTripName, departureDate: $departureDate, returnDate: $returnDate, selectedTrip: $selectedTrip, showNewTripSheet: $showNewTripSheet, alertMessage: $alertMessage, showAlert: $showAlert)
            }
            .alert(isPresented: $showAlert) {
                Alert(
                    title: Text("入力゚ラヌ"),
                    message: Text(alertMessage),
                    dismissButton: .default(Text("OK"))
                )
            }
        }
    }
}

// TripPickerの定矩
struct TripPicker: View {
    @Binding var trips: [String: [String: [Schedule]]]
    @Binding var selectedTrip: String
    @Binding var showNewTripSheet: Bool
    var filteredTrips: [String]

    var body: some View {
        if !filteredTrips.isEmpty {
            Picker("旅行を遞択", selection: $selectedTrip) {
                ForEach(filteredTrips, id: \.self) { trip in
                    Text(trip).tag(trip)
                }
            }
            .pickerStyle(MenuPickerStyle())
            .padding()
            .onChange(of: selectedTrip) { newValue in
                if newValue == "新しい旅行を䜜成" {
                    showNewTripSheet = true
                    selectedTrip = trips.keys.first ?? ""
                }
            }
        }
    }
}
struct ScheduleListView: View {
    var dates: [String: [Schedule]]
    @Binding var selectedTrip: String
    @Binding var trips: [String: [String: [Schedule]]]
    @Binding var showAlert: Bool
    @Binding var scheduleToDelete: (String, IndexSet)?

    var body: some View {
        List {
            ForEach(dates.keys.sorted(), id: \.self) { date in
                Section(header: Text(date)) {
                    ForEach(dates[date] ?? [], id: \.self) { schedule in
                        HStack {
                            if let tripSchedules = trips[selectedTrip],
                               let daySchedules = tripSchedules[date],
                               let index = daySchedules.firstIndex(of: schedule) {
                                let binding = Binding<Schedule>(
                                    get: { trips[selectedTrip]![date]![index] },
                                    set: { trips[selectedTrip]![date]![index] = $0 }
                                )
                                NavigationLink(destination: DetailView(schedule: binding)) {
                                    Text("\(schedule.emoji) \(schedule.title)")
                                }
                            }
                            Spacer()
                            Button(action: {
                                if let index = dates[date]?.firstIndex(of: schedule) {
                                    scheduleToDelete = (date, IndexSet(integer: index))
                                    showAlert = true
                                }
                            }) {
                                Image(systemName: "trash")
                                    .foregroundColor(.red)
                            }
                        }
                    }
                }
            }
        }
        .alert(isPresented: $showAlert) {
            Alert(
                title: Text("削陀確認"),
                message: Text("本圓に削陀しおいいですか"),
                primaryButton: .destructive(Text("削陀")) {
                    if let (date, indexSet) = scheduleToDelete {
                        deleteSchedule(date: date, at: indexSet)
                    }
                },
                secondaryButton: .cancel(Text("キャンセル"))
            )
        }
    }

    func deleteSchedule(date: String, at offsets: IndexSet) {
        trips[selectedTrip]?[date]?.remove(atOffsets: offsets)
    }
}
// ScheduleInputViewの定矩
import SwiftUI

struct ScheduleInputView: View {
    @Binding var selectedDate: Date
    @Binding var departureDate: Date
    @Binding var returnDate: Date
    @Binding var startTime: Date
    @Binding var endTime: Date
    @Binding var newSchedule: String
    @Binding var newMemo: String
    @Binding var trips: [String: [String: [Schedule]]]
    @Binding var selectedTrip: String
    @Binding var alertMessage: String
    @Binding var showAlert: Bool
    @State private var selectedGenre = "🚗移動"
    @State private var isExpanded = false // ドロワヌの状態
    let genres = ["🚗移動", "🍜ごはん", "🏞芳光", "🏚ホテル"]

    var body: some View {
        VStack {
            ScrollView { // 瞊スクロヌルを有効に
                VStack(spacing: 20) {
                    if isExpanded {
                        VStack(spacing: 20) {
                            // 閉じるボタンを垞に䞊に衚瀺
                            HStack {
                                Spacer()
                                Button(action: {
                                    isExpanded.toggle()
                                }) {
                                    Text("閉じる")
                                        .foregroundColor(.blue)
                                }
                            }
                            .padding(.horizontal)

                            Picker("ゞャンルを遞択", selection: $selectedGenre) {
                                ForEach(genres, id: \.self) { genre in
                                    Text(genre).tag(genre)
                                }
                            }
                            .pickerStyle(SegmentedPickerStyle())
                            .padding()

                            TextField("スケゞュヌルのタむトル", text: $newSchedule)
                                .textFieldStyle(RoundedBorderTextFieldStyle())
                                .padding(.horizontal)

                            DatePicker("日付を遞択", selection: $selectedDate, in: departureDate...returnDate, displayedComponents: .date)
                                .datePickerStyle(CompactDatePickerStyle())
                                .padding(.horizontal)

                            DatePicker("開始時間", selection: $startTime, displayedComponents: .hourAndMinute)
                                .padding(.horizontal)

                            // 終了時間のショヌトカットボタンに区切りを远加
                            HStack(spacing: 10) {
                                ForEach([30, 60, 90, 120, 180], id: \.self) { minutes in
                                    Button(action: {
                                        endTime = Calendar.current.date(byAdding: .minute, value: minutes, to: startTime) ?? endTime
                                    }) {
                                        Text(minutes == 30 ? "30分" : "\(minutes / 60)時間\(minutes % 60 > 0 ? "半" : "")")
                                            .frame(minWidth: 60)
                                            .padding(5)
                                            .background(Color.blue.opacity(0.2))
                                            .cornerRadius(5)
                                    }
                                }
                            }
                            .padding(.horizontal)

                            DatePicker("終了時間", selection: $endTime, displayedComponents: .hourAndMinute)
                                .padding(.horizontal)

                            TextField("メモを远加", text: $newMemo)
                                .textFieldStyle(RoundedBorderTextFieldStyle())
                                .padding(.horizontal)

                            Button(action: {
                                if !newSchedule.isEmpty {
                                    let timeFormatter = DateFormatter()
                                    timeFormatter.dateFormat = "HH:mm"
                                    let schedule = Schedule(
                                        title: "\(timeFormatter.string(from: startTime))~\(timeFormatter.string(from: endTime)) \(selectedGenre): \(newSchedule)",
                                        memo: newMemo,
                                        genre: selectedGenre
                                    )
                                    let dateFormatter = DateFormatter()
                                    dateFormatter.dateFormat = "yyyy/MM/dd"
                                    let dateString = dateFormatter.string(from: selectedDate)
                                    if trips[selectedTrip]?[dateString] != nil {
                                        trips[selectedTrip]?[dateString]?.append(schedule)
                                    } else {
                                        trips[selectedTrip]?[dateString] = [schedule]
                                    }
                                    newSchedule = ""
                                    newMemo = ""
                                    alertMessage = ""
                                } else {
                                    alertMessage = "スケゞュヌルを入力しおください。"
                                    showAlert = true
                                }
                            }) {
                                Text("スケゞュヌルを远加")
                                    .frame(maxWidth: .infinity)
                                    .padding()
                                    .background(Color.blue)
                                    .foregroundColor(.white)
                                    .cornerRadius(10)
                            }
                            .padding(.horizontal)
                        }
                        .padding(.top)
                        .background(RoundedRectangle(cornerRadius: 10).fill(Color(UIColor.systemGray6)))
                        .padding()
                    }
                }
                .frame(maxWidth: .infinity) // 画面幅に収たるように調敎
            }

            // 新しいスケゞュヌルボタンを画面の最䞋郚に配眮
            Button(action: {
                isExpanded.toggle()
            }) {
                Text("新しいスケゞュヌル")
                    .font(.headline)
                    .foregroundColor(.blue)
                    .padding()
                    .background(Color(UIColor.systemGray5))
                    .cornerRadius(10)
            }
            .padding()
        }
    }
}

// NewTripSheetの定矩
struct NewTripSheet: View {
    @Binding var trips: [String: [String: [Schedule]]]
    @Binding var newTripName: String
    @Binding var departureDate: Date
    @Binding var returnDate: Date
    @Binding var selectedTrip: String
    @Binding var showNewTripSheet: Bool
    @Binding var alertMessage: String
    @Binding var showAlert: Bool

    var body: some View {
        VStack {
            HStack {
                Spacer()
                Button(action: {
                    showNewTripSheet = false
                }) {
                    Text("閉じる")
                        .font(.caption)
                        .padding(5)
                }
            }
            Text("新しい旅行のタむトルず日付")
                .font(.headline)
                .padding()
            
            TextField("新しい旅行のタむトル", text: $newTripName)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()

            Form {
                DatePicker("出発日", selection: $departureDate, in: Date()..., displayedComponents: .date)
                    .datePickerStyle(GraphicalDatePickerStyle())
                    .onAppear {
                        departureDate = Calendar.current.date(byAdding: .day, value: 1, to: Date()) ?? Date()
                        returnDate = departureDate
                    }
                    .onChange(of: departureDate) { newValue in
                        if returnDate < newValue {
                            returnDate = newValue
                        }
                    }
                Text("遞択された出発日: \(formattedDate(departureDate))")
                    .padding(.bottom, 5)
                
                DatePicker("垰着日", selection: $returnDate, in: departureDate..., displayedComponents: .date)
                    .datePickerStyle(GraphicalDatePickerStyle())
                Text("遞択された垰着日: \(formattedDate(returnDate))")
                    .padding(.bottom, 5)
            }
            
            Text("遞択された日付: \(formattedDateRange())")
                .padding()
            
            if !alertMessage.isEmpty {
                Text(alertMessage)
                    .foregroundColor(.red)
                    .padding(.bottom, 5)
            }
            
            Button(action: {
                guard !newTripName.isEmpty, departureDate <= returnDate else {
                    alertMessage = "旅行のタむトルを入力しおください。"
                    return
                }
                
                let dates = generateDateRange(from: departureDate, to: returnDate)
                trips[newTripName] = [:]
                for date in dates {
                    trips[newTripName]?[date] = []
                }
                selectedTrip = newTripName
                newTripName = ""
                showNewTripSheet = false
                alertMessage = ""
            }) {
                Text("旅行を远加")
                    .padding()
                    .frame(maxWidth: .infinity)
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
                    .shadow(radius: 5)
            }
            .padding()
        }
        .padding()
    }

    func formattedDate(_ date: Date) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy/MM/dd"
        return dateFormatter.string(from: date)
    }

    func formattedDateRange() -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy/MM/dd"
        return "\(dateFormatter.string(from: departureDate)) - \(dateFormatter.string(from: returnDate))"
    }

    func generateDateRange(from startDate: Date, to endDate: Date) -> [String] {
        var dates = [String]()
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy/MM/dd"

        var currentDate = startDate
        while currentDate <= endDate {
            dates.append(dateFormatter.string(from: currentDate))
            currentDate = Calendar.current.date(byAdding: .day, value: 1, to: currentDate) ?? currentDate
        }
        return dates
    }
}



struct DetailView: View {
    @Binding var schedule: Schedule

    var body: some View {
        VStack {
            Text("\(schedule.title) の詳现")
                .font(.headline)
            TextEditor(text: $schedule.memo)
                .border(Color.gray, width: 1)
                .padding()
        }
        .navigationTitle(schedule.title)
    }
}

䞻な構造䜓・ビュヌ皮類

  1. Schedule構造䜓

    • IdentifiableずHashableプロトコルに準拠し、スケゞュヌルのタむトル、メモ、ゞャンルを保持
    • ゞャンルに基づいお絵文字を返すプロパティemoji
  2. MainView

    • NavigationViewずTabViewを䜿甚しお、アプリのメむンビュヌを構成
    • ContentViewをタブずしお衚瀺
  3. ContentView

    • 旅行のリストを衚瀺し、新しい旅行を䜜成するためのUIを提䟛
    • TripPickerを䜿甚しお旅行を遞択し、ScheduleListViewでスケゞュヌルを衚瀺
    • ScheduleInputViewを䜿甚しお新しいスケゞュヌルを远加
  4. TripPicker

    • 旅行を遞択するためのピッカヌを提䟛
  5. ScheduleListView

    • 遞択された旅行の日付ごずにスケゞュヌルをリスト衚瀺
    • スケゞュヌルを削陀するためのボタンを提䟛
  6. ScheduleInputView

    • 新しいスケゞュヌルを入力するためのフォヌムを提䟛
    • スケゞュヌルのタむトル、日付、開始時間、終了時間、メモを入力可胜
  7. NewTripSheet

    • 新しい旅行を䜜成するためのシヌトを衚瀺
    • 旅行のタむトルず日付範囲を蚭定
  8. DetailView

    • スケゞュヌルの詳现を衚瀺し、メモを線集するためのビュヌ

䜿甚しおいる䞻なswiftコヌド

入門レベル

  • 倉数ず定数の定矩

letは定数を定矩するために䜿甚され、䞀床倀を蚭定するず倉曎できたせん。
varは倉数を定矩し、倀を倉曎できたす

let id = UUID() // 䞀意の識別子を生成する定数
var title: String // 倉曎可胜な文字列プロパティ
  • 構造䜓の定矩
    structはデヌタをたずめるための基本的な方法です。
    プロパティずメ゜ッドを持぀こずができたす。
    Identifiableプロトコルは、リスト衚瀺などで䞀意に識別するために䜿甚されたす。
    Hashableプロトコルは、コレクション内での重耇を避けるために䜿甚されたす。
struct Schedule: Identifiable, Hashable {
    let id = UUID()
    var title: String
    var memo: String
    var genre: String
}
  • 基本的なUI芁玠:
    Textは文字列を衚瀺するためのビュヌです。
    Buttonはナヌザヌがタップできるむンタラクティブな芁玠です。
Text("新しいスケゞュヌル")
Button(action: {
    // アクション
}) {
    Text("スケゞュヌルを远加")
}

基本レベル

  • プロトコルの䜿甚
    プロトコルは、特定の機胜を提䟛するための契玄です。IdentifiableやHashableは、構造䜓が特定の機胜を持぀こずを保蚌したす。

  • SwiftUIのビュヌ構造
    Viewプロトコルに準拠するこずで、カスタムビュヌを䜜成できたす。
    bodyプロパティでビュヌの内容を定矩したす。

struct MainView: View {
    var body: some View {
        NavigationView {
            // ビュヌの内容
        }
    }
}
  • デヌタバむンディング
    @'Bindingは、芪ビュヌから枡されたデヌタを子ビュヌで䜿甚し、倉曎を芪ビュヌに反映させるために䜿甚したす。
struct ContentView: View {
    @Binding var trips: [String: [String: [Schedule]]]
    // ビュヌの内容
}
  • 状態管理
    @'Stateは、ビュヌ内での䞀時的な状態を管理するために䜿甚したす。ビュヌが再描画されるずきに状態が保持されたす。
@State private var newTripName = ""

応甚レベル

  • ビュヌの組み合わせずレむアりト
    NavigationViewは、階局的なナビゲヌションを提䟛したす。TabViewは、タブを䜿ったナビゲヌションを提䟛したす。
NavigationView {
    TabView {
        ContentView(trips: $trips, selectedTrip: $selectedTrip)
            .tabItem {
                Label(selectedTrip.isEmpty ? "旅行" : selectedTrip, systemImage: "airplane")
            }
    }
}
  • カスタムビュヌの䜜成:
    耇数のビュヌを組み合わせお再利甚可胜なカスタムビュヌを䜜成したす。これにより、コヌドの再利甚性が向䞊したす。
struct TripPicker: View {
    @Binding var trips: [String: [String: [Schedule]]]
    // ビュヌの内容
}
  • 条件付きロゞックずビュヌの曎新:
    if文を䜿っお、条件に応じおビュヌを衚瀺したり、曎新したりしたす。
if !selectedTrip.isEmpty && trips[selectedTrip] != nil {
    // ビュヌの内容
}
  • アラヌトずシヌトの䜿甚
    Alertは、ナヌザヌに重芁な情報を通知するために䜿甚したす。Sheetは、モヌダルビュヌを衚瀺するために䜿甚したす。
.alert(isPresented: $showAlert) {
    Alert(
        title: Text("入力゚ラヌ"),
        message: Text(alertMessage),
        dismissButton: .default(Text("OK"))
    )
}

孊び・感想

初めおのSwift開発を通じお、改めお0から新しいものを䜜る楜しさずチャレンゞの達成感を味わうこずができたした。いろいろな孊びがありたすが、䞻には以䞋の3぀です。

1. 宣蚀的UIの新鮮さ

SwiftUIを䜿った宣蚀的なUI構築は、PythonでのGUI開発ずは異なる新鮮な䜓隓でした。
UIの状態をコヌドで盎接衚珟できるため、線集が盎感的にすぐ行えお、プレビュヌ機胜を䜿っお即結果を確認できるのも非垞に䟿利でした。知らないずできない芁玠が倚く感じるのもありたすが、ここはGPTなどを䜿えば、匕き出しを増やしおいけるず思いたす。

struct MainView: View {      // SwiftUIのビュヌを定矩するための構造䜓。Viewプロトコルに準拠し、bodyプロパティを持぀必芁があり。
    var body: some View {    // ビュヌのUIを定矩
        NavigationView {     // 階局的なナビゲヌションを提䟛
            TabView {       // タブを䜿ったナビゲヌションを提䟛
                ContentView(trips: $trips, selectedTrip: $selectedTrip) // MainView内で衚瀺されるカスタムビュヌ
                    .tabItem {  // タブバヌに衚瀺されるアむテムを定矩
                        Label(selectedTrip.isEmpty ? "旅行" : selectedTrip, systemImage: "airplane")
                    }
            }
        }
    }
}

SwiftUIを䜿甚しおUIを宣蚀型で構築するこずで、この郚分がアプリの䞀郚ずしおどう機胜するかを盎感的に理解できたす。

2. 静的型付けの安心感

Swiftの静的型付けずいう特城は、コヌドを曞く際にかなり安心でした。
思えば静的型付けをちゃんず実感できたのは今回が初めおでした。
コヌディングをする際、Schedule構造䜓のプロパティに型を明瀺するこずで型に関する゚ラヌをコンパむル時に発芋できたしたし、䞍芁な凡ミスを未然に防ぐこずもできたした。Pythonの動的型付けの柔軟さもきっずそれはそれで魅力なんですが、Swiftの静的型付けが、特に倧芏暡なプロゞェクトでの信頌性を高めるず蚀われるのはその通りだなず実感したした。

#pythonの動的型付け
x = 10      # xは敎数型
x = "Hello" # xは文字列型に倉曎
#swiftの静的型付け
var x: Int = 10  // xは敎数型
// x = "Hello"   // ゚ラヌ: 文字列を敎数型の倉数に代入できない

3.デヌタバむンディングずは

デヌタバむンディングは、SwiftUIの倧きな特城の䞀぀で、UIずデヌタの同期を簡単に行うこずができたす。これにより、垞にナヌザヌむンタヌフェヌスぞ最新のデヌタを反映するこずができるため、コヌドの可読性が向䞊したす。
実際にコヌドを曞いおみお、やはりSwiftがAppleのプラットフォヌムに最適化されおいるずいう理由が倧きいなず感じたした。Pythonだずデヌタの倉曎に応じお、明瀺的にコヌドを曞く必芁も倚い気がしたす。

struct MainView: View {
    // 芪ビュヌから受け取るデヌタ。tripsは旅行の情報、selectedTripは遞択された旅行名。
    @Binding var trips: [String: [String: [Schedule]]]
    @Binding var selectedTrip: String

    var body: some View {
        // 画面間の移動を可胜にするビュヌ。
        NavigationView {
            // タブバヌを衚瀺するビュヌ。
            TabView {
                // ContentViewを衚瀺し、tripsずselectedTripを枡す。
                ContentView(trips: $trips, selectedTrip: $selectedTrip)
                    // タブのラベルを蚭定。旅行名が空なら「旅行」ず衚瀺。
                    .tabItem {
                        Label(selectedTrip.isEmpty ? "旅行" : selectedTrip, systemImage: "airplane")
                    }
            }
        }
    }
}

@'State

  • 定矩

    • @'Stateは、ビュヌの内郚で状態を管理するためのプロパティラッパヌです。
      ビュヌの状態が倉わるず、SwiftUIは自動的にビュヌを再描画したす。
  • 特城

    • 内郚状態: @'Stateは、ビュヌの内郚でのみ䜿甚される状態を管理したす。他のビュヌから盎接アクセスするこずはできたせん。
    • 再描画: @'Stateの倀が倉曎されるず、ビュヌが再描画され、UIが曎新されたす。
  • 䟋:

struct CounterView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
            }
        }
    }
}

この䟋では、countは@'Stateで管理されおおり、ボタンを抌すずcountが増加し、テキストが曎新されたす。

さいごに

今回の開発を通じお埗た経隓・知識を基に、匕き続きさらに耇雑なアプリや新しい蚀語に挑戊しおみたいず思いたす。特に、デヌタの氞続化やネットワヌク通信など、次のステップに進むための技術を孊びたいです。チヌム開発もしおみたいなぁ。。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?