6
2

はじめに

こんにちは!
先日、以下の記事を書きました。

Swift & SwiftUIでタロットカードアプリを作る
[Swift] ホラー画像生成アプリ

今回はこれらをマージして、タロットカードの画像を自動生成してみました。
簡易版ですが、動きは良さそうなので共有します!

要件

  • ボタンをタップすると、タロットカードをランダムに選択する
  • タロットカードの名前と意味に沿って画像を生成する
  • タロットカードの名前、画像、意味を表示する

準備

プロジェクトの作成方法は、Swift & SwiftUIでタロットカードアプリを作る を参照してください。

User InterfaceはSwiftUIを選択しました。

実装

ContentViewを修正

ContentView.swift
//
//  ContentView.swift
//  TarotApp
//

import SwiftUI

struct ContentView: View {
    @State private var selectedCard: TarotCard?

    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                Text("TarotApp")
                    .font(.largeTitle)
                    .fontWeight(.bold)
                    .padding()
                    .frame(maxWidth: .infinity) // タイトルを中央寄せにする
                    .background(Color.gray.opacity(0.2))
                    .cornerRadius(10)

                if let card = selectedCard {
                    CardView(card: card)
                        .id(card.id)
                    Button("もう一度引く") {
                        // ランダムにカードを選択
                        selectedCard = tarotCards.randomElement()
                    }
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
                } else {
                    Button("カードを引く") {
                        // ランダムにカードを選択
                        selectedCard = tarotCards.randomElement()
                    }
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

CardViewも修正

CardView.swift
//  CardView.swift
//  TarotApp
//

import SwiftUI

struct CardView: View {
    let card: TarotCard

    // OpenAI APIキーを設定
    private let apiKey = "sk-proj-xxx"

    // 画状態変数を追加
    @State private var outputImage: UIImage?
    @State private var isLoading = false
    @State private var showAlert = false

    var body: some View {
        VStack(spacing: 10) {
            Text(card.name)
                .font(.largeTitle)
                .fontWeight(.bold)
                .padding()
                .background(Color.gray.opacity(0.2))
                .cornerRadius(10)

            Text(card.description)
                .font(.headline)
                .multilineTextAlignment(.center)
                .padding()

            // 画像を表示
            if let outputImage = outputImage {
                Image(uiImage: outputImage)
                    .resizable()
                    .scaledToFit()
                    .cornerRadius(10)
                    .padding()
            } else {
                // 画像がない場合の表示
                ProgressView()
                    .opacity(isLoading ? 1.0 : 0.0)
                    .padding()
            }

            Spacer()

            Text("意味")
                .font(.title2)
                .fontWeight(.bold)
                .padding()

            Text(card.meaning)
                .font(.body)
                .padding()
        }
        .padding()
        .background(Color(UIColor.systemGray6))
        .cornerRadius(20)
        .shadow(radius: 5)
        .onAppear {
            // カード表示時に画像を生成
            isLoading = true
            generateImage(from: card.name, from: card.description)
        }
        // アラート表示
        .alert("エラー", isPresented: $showAlert) {
            Button("OK", role: .cancel) { }
        } message: {
            Text("画像の生成に失敗しました。")
        }
    }
  
    func generateImage(from name: String, from description: String) {
        let url = URL(string: "https://api.openai.com/v1/images/generations")!

        // ホラーテイストを必ず入れるためのプロンプト調整
        let horrorPrompt = "An eerie and unsettling scene with '\(name), \(description)', in the style of a horror and cyber-punk, dark and gruesome, high quality, digital art."

        let parameters: [String: Any] = [
            "model": "dall-e-3", // dall-e-3に指定
            "prompt": horrorPrompt,
            "n": 1,
            "size": "1024x1024" // 画像サイズ
        ]

        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.addValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try? JSONSerialization.data(withJSONObject: parameters)

        URLSession.shared.dataTask(with: request) { data, response, error in
            // エラー処理
            if let error = error {
                print("エラー: \(error)")
                DispatchQueue.main.async {
                    self.showAlert = true
                    self.isLoading = false // エラー時にローディングを停止
                }
                return
            }

            guard let data = data else {
                print("データがありません")
                DispatchQueue.main.async {
                    self.showAlert = true
                    self.isLoading = false // エラー時にローディングを停止
                }
                return
            }

            do {
                if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
                   let dataArray = json["data"] as? [[String: Any]],
                   let firstImageData = dataArray.first,
                   let imageUrlString = firstImageData["url"] as? String,
                   let imageUrl = URL(string: imageUrlString) {

                    // 画像の取得
                    if let imageData = try? Data(contentsOf: imageUrl), let image = UIImage(data: imageData) {
                        DispatchQueue.main.async {
                            self.outputImage = image
                            self.isLoading = false
                        }
                    } else {
                        print("画像の取得またはデコードエラー")
                        DispatchQueue.main.async {
                            self.showAlert = true
                            self.isLoading = false // エラー時にローディングを停止
                        }
                    }
                } else {
                    print("画像URLの解析エラー")
                    DispatchQueue.main.async {
                        self.showAlert = true
                        self.isLoading = false // エラー時にローディングを停止
                    }
                }

            } catch {
                print("JSON解析エラー: \(error)")
                DispatchQueue.main.async {
                    self.showAlert = true
                    self.isLoading = false // エラー時にローディングを停止
                }
            }
        }.resume()
    }
}

TarotCardsはそのまま使います。

TarotCards.swift
//
//  TarotCards.swift
//  TarotApp
//

import SwiftUI

struct TarotCard: Identifiable {
  let id = UUID()
  let name: String
  let description: String
  let meaning: String
}

let tarotCards: [TarotCard] = [
  TarotCard(name: "The Fool",
            description: "始まり、無邪気さ、新しい旅立ち",
            meaning: "新しい章の始まり、自由な精神、冒険心"),
  TarotCard(name: "The Magician",
            description: "意志力、創造力、潜在能力",
            meaning: "目標達成、能力発揮、潜在能力の開花"),
  TarotCard(name: "The High Priestess",
            description: "直感、神秘、秘密",
            meaning: "内なる知恵、直感力、秘密の解明"),
  TarotCard(name: "The Empress",
            description: "豊穣、愛情、自然",
            meaning: "豊かさ、愛情、創造性"),
  TarotCard(name: "The Emperor",
            description: "権力、安定、秩序",
            meaning: "安定、統率力、権威"),
  TarotCard(name: "The Hierophant",
            description: "伝統、信仰、精神的指導",
            meaning: "伝統、精神的な指導、社会規範"),
  TarotCard(name: "The Lovers",
            description: "愛、調和、選択",
            meaning: "愛情、調和、パートナーシップ"),
  TarotCard(name: "The Chariot",
            description: "意志、勝利、克服",
            meaning: "意志力、目標達成、克服"),
  TarotCard(name: "Strength",
            description: "強さ、勇気、忍耐",
            meaning: "内なる強さ、克服、忍耐"),
  TarotCard(name: "The Hermit",
            description: "内省、孤独、自己探求",
            meaning: "内観、自己成長、探求"),
  TarotCard(name: "Wheel of Fortune",
            description: "運命、変化、サイクル",
            meaning: "運命、変化、サイクル"),
  TarotCard(name: "Justice",
            description: "公正、均衡、法則",
            meaning: "公正、バランス、調和"),
  TarotCard(name: "The Hanged Man",
            description: "犠牲、視点を変える、新しい見方",
            meaning: "新しい視点、見直し、変化"),
  TarotCard(name: "Death",
            description: "終わり、変容、再生",
            meaning: "終わりと始まり、変容、再生"),
  TarotCard(name: "Temperance",
            description: "調和、バランス、忍耐",
            meaning: "バランス、調和、忍耐"),
  TarotCard(name: "The Devil",
            description: "誘惑、執着、物質主義",
            meaning: "誘惑、執着、依存"),
  TarotCard(name: "The Tower",
            description: "破壊、変化、混乱",
            meaning: "破壊、変革、混乱"),
  TarotCard(name: "The Star",
            description: "希望、再生、癒し",
            meaning: "希望、再生、癒し"),
  TarotCard(name: "The Moon",
            description: "幻想、直感、隠された真実",
            meaning: "幻想、直感、隠された真実"),
  TarotCard(name: "The Sun",
            description: "喜び、成功、幸福",
            meaning: "喜び、成功、幸福"),
  TarotCard(name: "Judgement",
            description: "再生、自己評価、新しい始まり",
            meaning: "再生、自己評価、新しい始まり"),
  TarotCard(name: "The World",
            description: "完成、達成、統合",
            meaning: "完成、達成、統合"),
  TarotCard(name: "The Major Arcana",
            description: "運命のサイクル、人生の旅路",
            meaning: "人生の重要な課題、試練"),
  TarotCard(name: "Ace of Wands",
            description: "新しい始まり、情熱、創造性",
            meaning: "新しいプロジェクト、情熱、創造性"),
  TarotCard(name: "Two of Wands",
            description: "計画、展望、決断",
            meaning: "計画、展望、決断"),
  TarotCard(name: "Three of Wands",
            description: "拡大、成長、冒険",
            meaning: "拡大、成長、冒険"),
  TarotCard(name: "Four of Wands",
            description: "安定、祝賀、達成",
            meaning: "安定、祝賀、達成"),
  TarotCard(name: "Five of Wands",
            description: "競争、対立、混乱",
            meaning: "競争、対立、混乱"),
  TarotCard(name: "Six of Wands",
            description: "成功、勝利、栄光",
            meaning: "成功、勝利、栄光"),
  TarotCard(name: "Seven of Wands",
            description: "防衛、抵抗、忍耐",
            meaning: "防衛、抵抗、忍耐"),
  TarotCard(name: "Eight of Wands",
            description: "動き、変化、スピード",
            meaning: "動き、変化、スピード"),
  TarotCard(name: "Nine of Wands",
            description: "忍耐、決意、防衛",
            meaning: "忍耐、決意、防衛"),
  TarotCard(name: "Ten of Wands",
            description: "負担、責任、完成",
            meaning: "負担、責任、完成"),
  TarotCard(name: "Page of Wands",
            description: "新しいアイデア、熱意、探検",
            meaning: "新しいアイデア、熱意、探検"),
  TarotCard(name: "Knight of Wands",
            description: "行動力、情熱、冒険心",
            meaning: "行動力、情熱、冒険心"),
  TarotCard(name: "Queen of Wands",
            description: "独立心、自信、創造性",
            meaning: "独立心、自信、創造性"),
  TarotCard(name: "King of Wands",
            description: "リーダーシップ、成功、野心",
            meaning: "リーダーシップ、成功、野心"),
  TarotCard(name: "Ace of Cups",
            description: "愛情、感情、創造性",
            meaning: "愛情、感情、創造性"),
  TarotCard(name: "Two of Cups",
            description: "調和、愛情、パートナーシップ",
            meaning: "調和、愛情、パートナーシップ"),
  TarotCard(name: "Three of Cups",
            description: "祝賀、友情、喜び",
            meaning: "祝賀、友情、喜び"),
  TarotCard(name: "Four of Cups",
            description: "内省、満足、孤独",
            meaning: "内省、満足、孤独"),
  TarotCard(name: "Five of Cups",
            description: "悲しみ、喪失、失望",
            meaning: "悲しみ、喪失、失望"),
  TarotCard(name: "Six of Cups",
            description: "過去、思い出、ノスタルジア",
            meaning: "過去、思い出、ノスタルジア"),
  TarotCard(name: "Seven of Cups",
            description: "幻想、誘惑、選択",
            meaning: "幻想、誘惑、選択"),
  TarotCard(name: "Eight of Cups",
            description: "離脱、失望、新しい出発",
            meaning: "離脱、失望、新しい出発"),
  TarotCard(name: "Nine of Cups",
            description: "満足、幸福、願いの成就",
            meaning: "満足、幸福、願いの成就"),
  TarotCard(name: "Ten of Cups",
            description: "完璧な幸福、家庭、愛",
            meaning: "完璧な幸福、家庭、愛"),
  TarotCard(name: "Page of Cups",
            description: "新しい愛、感情、創造性",
            meaning: "新しい愛、感情、創造性"),
  TarotCard(name: "Knight of Cups",
            description: "ロマンチック、夢、理想",
            meaning: "ロマンチック、夢、理想"),
  TarotCard(name: "Queen of Cups",
            description: "共感、慈悲、直感",
            meaning: "共感、慈悲、直感"),
  TarotCard(name: "King of Cups",
            description: "感情の支配、感情の安定、調和",
            meaning: "感情の支配、感情の安定、調和"),
  TarotCard(name: "Ace of Swords",
            description: "新しいアイデア、決断、突破",
            meaning: "新しいアイデア、決断、突破"),
  TarotCard(name: "Two of Swords",
            description: "停滞、決断の遅延、葛藤",
            meaning: "停滞、決断の遅延、葛藤"),
  TarotCard(name: "Three of Swords",
            description: "悲しみ、失恋、苦痛",
            meaning: "悲しみ、失恋、苦痛"),
  TarotCard(name: "Four of Swords",
            description: "休息、回復、静寂",
            meaning: "休息、回復、静寂"),
  TarotCard(name: "Five of Swords",
            description: "敗北、裏切り、争い",
            meaning: "敗北、裏切り、争い"),
  TarotCard(name: "Six of Swords",
            description: "移転、旅立ち、癒し",
            meaning: "移転、旅立ち、癒し"),
  TarotCard(name: "Seven of Swords",
            description: "裏切り、詐欺、隠蔽",
            meaning: "裏切り、詐欺、隠蔽"),
  TarotCard(name: "Eight of Swords",
            description: "制限、束縛、囚われ",
            meaning: "制限、束縛、囚われ"),
  TarotCard(name: "Nine of Swords",
            description: "不安、恐怖、悪夢",
            meaning: "不安、恐怖、悪夢"),
  TarotCard(name: "Ten of Swords",
            description: "破壊、敗北、絶望",
            meaning: "破壊、敗北、絶望"),
  TarotCard(name: "Page of Swords",
            description: "知性、メッセージ、情報",
            meaning: "知性、メッセージ、情報"),
  TarotCard(name: "Knight of Swords",
            description: "迅速な行動、攻撃性、決断力",
            meaning: "迅速な行動、攻撃性、決断力"),
  TarotCard(name: "Queen of Swords",
            description: "独立心、知性、冷静",
            meaning: "独立心、知性、冷静"),
  TarotCard(name: "King of Swords",
            description: "権威、支配、正義",
            meaning: "権威、支配、正義"),
  TarotCard(name: "Ace of Pentacles",
            description: "豊かさ、財産、新しい始まり",
            meaning: "豊かさ、財産、新しい始まり"),
  TarotCard(name: "Two of Pentacles",
            description: "バランス、多忙、調整",
            meaning: "バランス、多忙、調整"),
  TarotCard(name: "Three of Pentacles",
            description: "チームワーク、職人技、協力",
            meaning: "チームワーク、職人技、協力"),
  TarotCard(name: "Four of Pentacles",
            description: "安全、安定、所有",
            meaning: "安全、安定、所有"),
  TarotCard(name: "Five of Pentacles",
            description: "貧困、失業、孤独",
            meaning: "貧困、失業、孤独"),
  TarotCard(name: "Six of Pentacles",
            description: "慈善、寛大、共有",
            meaning: "慈善、寛大、共有"),
  TarotCard(name: "Seven of Pentacles",
            description: "努力、辛抱、成果",
            meaning: "努力、辛抱、成果"),
  TarotCard(name: "Eight of Pentacles",
            description: "技能、熟練、努力",
            meaning: "技能、熟練、努力"),
  TarotCard(name: "Nine of Pentacles",
            description: "成功、豊かさ、満足",
            meaning: "成功、豊かさ、満足"),
  TarotCard(name: "Ten of Pentacles",
            description: "遺産、財産、家族",
            meaning: "遺産、財産、家族"),
  TarotCard(name: "Page of Pentacles",
            description: "新しい機会、学習、潜在能力",
            meaning: "新しい機会、学習、潜在能力"),
  TarotCard(name: "Knight of Pentacles",
            description: "勤勉、責任、安定",
            meaning: "勤勉、責任、安定"),
  TarotCard(name: "Queen of Pentacles",
            description: "豊穣、実用性、安定",
            meaning: "豊穣、実用性、安定"),
  TarotCard(name: "King of Pentacles",
            description: "富、成功、物質的な豊かさ",
            meaning: "富、成功、物質的な豊かさ")
]

動作確認

起動直後の画面。4回引いてみます。

1回目
Ace of Wands

2回目
Knight of Swords

3回目
The Emperor

4回目
The Fool

いい感じですね!
あとは画像サイズやデザインを工夫すれば、良さそうです。

最後に

今回は、タロットカードと画像生成を組み合わせてみました。
プロンプトによって様々なテイストの画像を生成できるので、みなさんもチャレンジしてみてください😀

6
2
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
6
2