私が開発したiPhoneアプリについて、経験を元に開発手法等の基礎的な知識から具体的な手順まで、分かりやすく解説していきます。
本記事ではスレッド内の投稿画面から投稿の内容を見る画面の作成方法について紹介します。アプリ開発はをしてみたい人の力になれましたら幸いです。
完成画面
投稿内容を表示する画面になります。ここから投稿された記事の本文など中身の詳細を確認することができます。
機能一覧
・CloudKitに登録されている投稿の内容を表示する機能
・いいねを押す機能
・投稿主を通報する機能
プログラム
以下が画面全体の説明になります。
import SwiftUI
struct ThreadInside: View {
//通報する画面に遷移するか判断
@State var report_state = false
//アラートの表示を判断する変数
@State private var showingAlert = false
//以下は前画面から投稿情報等を引き継ぐ
@Binding var id: String
@Binding var title: String
@Binding var date: Date
@Binding var selectedImaged:Data
@Binding var report: String
@Binding var post_name: String
@Binding var tf : Bool
@Binding var threadname: String
@Binding var thread_state: Bool
@AppStorage("islogin") var testAppStorage = false
@AppStorage("myname") var nameAppStorage = ""
@FetchRequest(sortDescriptors: []) var manages: FetchedResults<Manage>
@Environment(\.presentationMode) var presentationMode
@Environment(\.managedObjectContext) private var viewContext
@StateObject var viewModel = ThreadinListViewModel()
@ObservedObject var db_Model : DB_Model
var body: some View {
NavigationView {
ScrollView {
VStack{
//画像が登録されているかの条件分岐
if selectedImaged != Data.init() {
Image(uiImage: UIImage(data: selectedImaged)!).resizable().scaledToFit().frame(width: 350.0, height: 250.0)
}else{
Image("noimage").resizable().scaledToFit().frame(width: 350.0, height: 250.0)
}
//以下はUI部分を整えるための記載
Text(title).font(.title2).bold().frame(maxWidth: .infinity, alignment: .leading).padding(EdgeInsets(top: 0, leading: 10, bottom: 2, trailing: 10))
Text("\(date, formatter: itemFormatter)").font(.footnote).frame(maxWidth: .infinity, alignment: .leading).foregroundColor(.gray).padding(.leading, 13)
Text(report).lineSpacing(5).frame(maxWidth: .infinity, alignment: .leading).lineSpacing(5).padding(EdgeInsets(top: 3, leading: 13, bottom: 15, trailing: 10))
Text("投稿者:" + post_name).foregroundColor(.gray).frame(maxWidth: .infinity, alignment: .trailing).padding(.trailing, 10)
Divider().frame(height: 1)
.background(Color.green)
//投稿に対していいねをつけていたら
if tf {
Button(action: {
//いいねボタンを押された時の処理。Cloudkit、Firebaseの値を更新
Task {
await viewModel.updatedb_good(threadId: id, tf: tf, threadname: threadname)
}
tf = false
addManage()
}){
HStack {
//通報するボタンが押された時の処理。通報画面へ移動
Button(action: {
self.report_state.toggle()
}){
Text("通報する").bold().padding(.leading, 10)
}.sheet(isPresented: $report_state)
{
Inquiry(title: $title, post_name: $post_name, threadname: $threadname, thread_state:$thread_state,report_state:$report_state)
}
Spacer()
Image(systemName: "heart.fill").font(.system(size: 25)).foregroundColor(.red).padding(EdgeInsets(top: 10, leading: 0, bottom: 4, trailing: 25))
}
}
} else {
//ログインしていないユーザーの場合、いいねを押せないようにする(アラートを表示)
if nameAppStorage == "ゲスト" {
Button(action: {
self.showingAlert = true
}){
HStack {
Button(action: {
self.showingAlert = true
}){
Text("通報する").bold().padding(.leading, 10)
}.alert(isPresented: $showingAlert) { // ③アラートの表示条件設定
Alert(title: Text("ログインが必要です。"),
message: Text("こちらの機能はログインが必須となります。"),
primaryButton: .cancel(Text("閉じる")), // キャンセル用
secondaryButton: .destructive(Text("ログイン"), action: {
testAppStorage.toggle()
}))
}
Spacer()
Image(systemName: "heart").font(.system(size: 25)).foregroundColor(.red).padding(EdgeInsets(top: 10, leading: 0, bottom: 4, trailing: 25))
}
}.alert(isPresented: $showingAlert) { // ③アラートの表示条件設定
Alert(title: Text("ログインが必要です。"),
message: Text("こちらの機能はログインが必須となります。"),
primaryButton: .cancel(Text("閉じる")), // キャンセル用
secondaryButton: .destructive(Text("ログイン"), action: {
testAppStorage.toggle()
}))
}
} else {
Button(action: {
Task {
await viewModel.updatedb_good(threadId: id, tf: tf, threadname: threadname)
}
tf = true
addManage()
}){
HStack {
Button(action: {
self.report_state.toggle()
}){
Text("通報する").bold().padding(.leading, 10)
}.sheet(isPresented: $report_state)
{
Inquiry(title: $title, post_name: $post_name, threadname: $threadname, thread_state:$thread_state,report_state:$report_state)
}
Spacer()
Image(systemName: "heart").font(.system(size: 25)).foregroundColor(.red).padding(EdgeInsets(top: 10, leading: 0, bottom: 4, trailing: 25))
}
}
}
}
//画面のモーダル表示を閉じる処理
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}){
Text("閉じる").bold()
.padding(10)
.foregroundColor(Color.white)
.background(Color.blue)
}
}.navigationBarTitle(title, displayMode: .inline).bold()
}
}.navigationViewStyle(.stack)
}
//CoreDataのレコードを更新する処理(投稿に対していいねを押しているかどうかを判断)
func addManage() {
var new_manage = true
for manage in manages {
if manage.post_name == post_name && manage.title! == title {
manage.good_state = tf
db_Model.editManage(item: manage)
db_Model.writeManage(context: viewContext)
new_manage = false
}
}
if new_manage {
withAnimation {
print(threadname)
let newManage = Manage(context: viewContext)
newManage.thread_id = id
newManage.post_name = post_name
newManage.title = title
newManage.threadname = threadname
newManage.good_state = tf
newManage.report = report
newManage.timestamp = date
newManage.imagedata = selectedImaged
//データベース保存
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .gregorian)
formatter.locale = Locale(identifier: "ja_JP")
formatter.timeZone = TimeZone(identifier: "Asia/Tokyo")
formatter.dateFormat = "M/d(E) HH:mm"
return formatter
}()
以上が投稿の中身画面の説明になります。次は投稿する画面についての記事を出します。アプリ開発は初めての方にとっては難しいと思います。こ私のこの記事や他の開発に関する記事が見てくださる皆様の力になれますと幸いです。
コメント