IEには対応していません。Edgeでサイトを開く ≫

ソースコード source code

下記アプリの主要なソースコードを公開しています。アプリ開発の参考になれば幸いです。

画像等が別途必要ですので下記情報のみでアプリが完成するものではありません。 アプリは少しずつ機能拡張していますのでストア公開されているアプリと内容が異なる場合があります。 コードはコピーして自由にお使いいただけます。ただし著作権は放棄しておりませんので全部の再掲載はご遠慮ください。部分的に再掲載したり、改変して再掲載するのは構いません。 自身のアプリ作成の参考として個人使用・商用問わず自由にお使いいただけます。 コード記述のお手本を示すものではありません。ミニアプリですので変数名などさほど気遣いしていない部分も有りますし間違いも有るかと思いますので参考程度にお考え下さい。 他の賢者の皆様が公開されているコードを参考にした箇所も含まれます。iOSアプリ開発の熟練者が書いたコードではありません。 エンジニア向け技術情報共有サービスではありませんので説明は省いています。ご了承ください。 GitHubなどへの公開は予定しておりません。

下記コードの最終ビルド日: 2021-04-23

ConstValue.swift

//
//  ConstValue.swift
//  LuckyBox
//
//  Created by akira ohmachi on 2021/04/07.
//

import SwiftUI

class ConstValue: ObservableObject {

    //background
    static let colorBg: Color = Color.init(red:0,green:0.613,blue:0.203)
    
    //button color
    static let colorStart: Color = Color.init(red:0.238, green:0.77, blue:0.387)
    static let colorSetting: Color = Color.init(red:0.543, green:0.77, blue:0.289)
}

ContentView.swift

//
//  ContentView.swift
//  LuckyBox
//
//  Created by akira ohmachi on 2021/04/07.
//

import SwiftUI
import GoogleMobileAds

struct ContentView: View {
    @EnvironmentObject var pub: PublicManager
    @State private var boxImageName: String = "box_001"
    private var boxImageNamePrefix: String = "box_"
    @State private var timeLine: Int = 0
    @State private var busyFlag: Bool = false   //実行中
    @State private var destroyFlag: Bool = false
    @State private var emptyTextAlpha: Double = 0.0
    @State private var resultTextName: String = ""  //当選名
    @State private var resultTextAlpha: Double = 0.0
    @State private var resultTextPositionX: CGFloat = 0.45
    @State private var resultTextPositionY: CGFloat = 0.66
    //PIN input
    @State private var isInputPin = false   //trueでPIN入力ダイアログが表示される
    @State var pin: String = "" //ダイアログで入力したPIN
    //
    var body: some View {
        NavigationView {
            GeometryReader { bodyView in
                VStack(spacing: 0) {
                    HStack(spacing: 0) {
                        Button(action:{
                            self.startAction()
                        }){
                            Text("start")
                                .frame(width:bodyView.size.width / 4 * 3, height: bodyView.size.height / 6, alignment: .center)
                                .background(ConstValue.colorStart)
                                .foregroundColor(Color.white)
                        }
                        Rectangle().fill(Color.white).frame(width: 1,height: bodyView.size.height / 6)
                        NavigationLink(destination: SettingView(),isActive: self.$pub.isSetting) {
                            Button(action:{
                                if self.pub.pin == "" {
                                    self.pin = ""
                                    self.buttonActionSetting()
                                } else {
                                    self.isInputPin = true
                                }
                            }){
                                Text("setting")
                                    .frame(width: bodyView.size.width / 4, height: bodyView.size.height / 6, alignment: .center)
                                    .background(ConstValue.colorSetting)
                                    .foregroundColor(Color.white)
                                TextFieldAlertView(
                                    text: $pin,
                                    isShowingAlert: $isInputPin,
                                    placeholder: "",
                                    isSecureTextEntry: false,
                                    title: "PIN",
                                    message: "Enter PIN",
                                    leftButtonTitle: "Cancel",
                                    rightButtonTitle: "OK",
                                    leftButtonAction: {
                                        self.pin = ""
                                    },
                                    rightButtonAction: {
                                        self.buttonActionSetting()
                                    }
                                )
                            }
                        }
                        .background(ConstValue.colorSetting)
                    }
                    .frame(width:bodyView.size.width, height:bodyView.size.height / 6)
                    Rectangle().fill(Color.white).frame(width: bodyView.size.width,height: 1)
                    ZStack(alignment: .bottom) {
                        ScrollView {
                            Text("empty")
                                .foregroundColor(Color.white)
                                .opacity(self.emptyTextAlpha)
                                .padding(10)
                            Image(self.boxImageName)
                                .resizable()
                                .frame(width: bodyView.size.width, height: bodyView.size.width, alignment: .center)
                                .overlay (
                                    Text(self.resultTextName)
                                        .frame(width:bodyView.size.width, height:bodyView.size.width, alignment: .topLeading)
                                        .foregroundColor(Color.black)
                                        .font(.title2)
                                        .opacity(self.resultTextAlpha)
                                        .offset(CGSize(width: bodyView.size.width * self.resultTextPositionX, height: bodyView.size.width * self.resultTextPositionY))
                                )
                            Spacer(minLength: 150)
                        }
                        HStack(spacing: 0) {
                            Spacer(minLength: 0)
                            PublicManager.AdView().frame(maxWidth: bodyView.size.width, maxHeight: 50)
                            Spacer(minLength: 0)
                        }.background(Color(red:0.2,green:0.2,blue:0.2))
                    }
                }
                .background(ConstValue.colorBg)
                //.navigationBarTitle("")
                .navigationBarHidden(true)
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
        .onAppear {     //アプリ起動時に実行
            self.pub.loadItems()
        }
        .onChange(of: self.pub.isSetting) { _ in    //SettingViewからの戻り
            if self.pub.isSetting == false {
                self.emptyTextAlpha = 0.0
            }
        }
        .onDisappear {     //アプリ終了時
            self.destroyFlag = true
        }
    }
    
    //goto setting
    private func buttonActionSetting() {
        if self.busyFlag {
            return
        }
        if self.pin != self.pub.pin {
            self.pin = ""
            return
        }
        self.emptyTextAlpha = 0.0
        self.pin = ""
        for i in 0..<self.pub.items.count {
            self.pub.tmpItems[i].name = self.pub.items[i].name
            self.pub.tmpItems[i].qty = String(self.pub.items[i].qty)
        }
        self.pub.tmpPin = self.pub.pin
        self.pub.isSetting = true
    }

    //start button on click
    private func startAction() {
        if self.busyFlag {
            return
        }
        self.busyFlag = true
        self.resultTextName = ""
        self.resultTextAlpha = 0.0
        //textを選ぶ
        var alternatives: Array<Int> = []
        for i in 0..<self.pub.items.count {
            for _ in 0..<self.pub.items[i].qty {
                alternatives.append(i)
            }
        }
        if alternatives.count == 0 {    //選ぶものが無くなった
            self.timeLine = 1
            self.setImageName()
            self.emptyTextAlpha = 1.0
            self.busyFlag = false
            return
        }
        alternatives = alternatives.shuffled()  //alternatives[0]に決定
        self.resultTextName = self.pub.items[alternatives[0]].name
        self.pub.items[alternatives[0]].qty -= 1    //選んだクジを消費する
        self.pub.saveItems()
        //
        self.timeLine = 0
        self.timeLineAnimation()
    }

    //animation
    private func timeLineAnimation() {
        withAnimation {
            if self.destroyFlag {
                return
            }
            self.timeLine += 1
            if self.timeLine > 120 {
                self.busyFlag = false
                return
            }
            self.setImageName()
            self.resultTextDraw()
            DispatchQueue.global().async {
                Thread.sleep(forTimeInterval: 0.02)
                DispatchQueue.main.sync {
                    if self.destroyFlag == false {
                        self.timeLineAnimation()
                    }
                }
            }
        }
    }

    private func setImageName() {
        self.boxImageName = self.boxImageNamePrefix + String(format: "%03d",self.timeLine)
    }

    private func resultTextDraw() {
        let actionStart: Int = 105
        if self.timeLine < actionStart {
            return
        }
        let actionSpan: Int = 120 - actionStart
        self.resultTextAlpha = Double(actionSpan - (120 - self.timeLine)) / Double(120 - actionStart)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView().environmentObject(PublicManager())
        }
    }
}

LuckyBoxApp.swift

//
//  LuckyBoxApp.swift
//  LuckyBox
//
//  Created by akira ohmachi on 2021/04/07.
//

import SwiftUI
import UIKit
import GoogleMobileAds

// AppDelegateクラスを定義する
class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {

        // Mobile Ads SDKを初期化する
        GADMobileAds.sharedInstance().start(completionHandler: nil)
        
        return true
    }
}


@main
struct LuckyBoxApp: App {
    // SwiftUI AppライフサイクルにAppDelegateクラスを注入する
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(PublicManager())
        }
    }
}

PublicManager.swift

//
//  PublicManager.swift
//  LuckyBox
//
//  Created by akira ohmachi on 2021/04/07.
//

import SwiftUI
import GoogleMobileAds

class PublicManager: ObservableObject {
    
    @Published var isSetting: Bool = false
    
    @AppStorage("item1name") var item1name: String = ""
    @AppStorage("item2name") var item2name: String = ""
    @AppStorage("item3name") var item3name: String = ""
    @AppStorage("item4name") var item4name: String = ""
    @AppStorage("item5name") var item5name: String = ""
    @AppStorage("item6name") var item6name: String = ""
    @AppStorage("item7name") var item7name: String = ""
    @AppStorage("item8name") var item8name: String = ""
    @AppStorage("item9name") var item9name: String = ""
    @AppStorage("item10name") var item10name: String = ""

    @AppStorage("item1qty") var item1qty: Int = 0
    @AppStorage("item2qty") var item2qty: Int = 0
    @AppStorage("item3qty") var item3qty: Int = 0
    @AppStorage("item4qty") var item4qty: Int = 0
    @AppStorage("item5qty") var item5qty: Int = 0
    @AppStorage("item6qty") var item6qty: Int = 0
    @AppStorage("item7qty") var item7qty: Int = 0
    @AppStorage("item8qty") var item8qty: Int = 0
    @AppStorage("item9qty") var item9qty: Int = 0
    @AppStorage("item10qty") var item10qty: Int = 0
 
    @AppStorage("pin") var pin: String = ""
    @Published var tmpPin: String = ""

    struct Item {
        var name: String
        var qty: Int
        init(name: String, qty: Int) {
            self.name = name
            self.qty = qty
        }
    }
    
    var items: Array<Item> = [
        Item(name: "", qty: 0),
        Item(name: "", qty: 0),
        Item(name: "", qty: 0),
        Item(name: "", qty: 0),
        Item(name: "", qty: 0),
        Item(name: "", qty: 0),
        Item(name: "", qty: 0),
        Item(name: "", qty: 0),
        Item(name: "", qty: 0),
        Item(name: "", qty: 0),
    ]

    struct TmpItem {
        var name: String
        var qty: String
        init(name: String, qty: String) {
            self.name = name
            self.qty = qty
        }
    }

    var tmpItems: Array<TmpItem> = [
        TmpItem(name: "", qty: ""),
        TmpItem(name: "", qty: ""),
        TmpItem(name: "", qty: ""),
        TmpItem(name: "", qty: ""),
        TmpItem(name: "", qty: ""),
        TmpItem(name: "", qty: ""),
        TmpItem(name: "", qty: ""),
        TmpItem(name: "", qty: ""),
        TmpItem(name: "", qty: ""),
        TmpItem(name: "", qty: ""),
    ]

    func saveItems() {
        self.item1name = self.items[0].name
        self.item1qty = self.items[0].qty
        self.item2name = self.items[1].name
        self.item2qty = self.items[1].qty
        self.item3name = self.items[2].name
        self.item3qty = self.items[2].qty
        self.item4name = self.items[3].name
        self.item4qty = self.items[3].qty
        self.item5name = self.items[4].name
        self.item5qty = self.items[4].qty
        self.item6name = self.items[5].name
        self.item6qty = self.items[5].qty
        self.item7name = self.items[6].name
        self.item7qty = self.items[6].qty
        self.item8name = self.items[7].name
        self.item8qty = self.items[7].qty
        self.item9name = self.items[8].name
        self.item9qty = self.items[8].qty
        self.item10name = self.items[9].name
        self.item10qty = self.items[9].qty
    }
    
    func loadItems() {
        self.items[0].name = self.item1name
        self.items[0].qty = self.item1qty
        self.items[1].name = self.item2name
        self.items[1].qty = self.item2qty
        self.items[2].name = self.item3name
        self.items[2].qty = self.item3qty
        self.items[3].name = self.item4name
        self.items[3].qty = self.item4qty
        self.items[4].name = self.item5name
        self.items[4].qty = self.item5qty
        self.items[5].name = self.item6name
        self.items[5].qty = self.item6qty
        self.items[6].name = self.item7name
        self.items[6].qty = self.item7qty
        self.items[7].name = self.item8name
        self.items[7].qty = self.item8qty
        self.items[8].name = self.item9name
        self.items[8].qty = self.item9qty
        self.items[9].name = self.item10name
        self.items[9].qty = self.item10qty

        var empty: Bool = true
        for i in 0..<items.count {
            if self.items[i].name != "" {
                empty = false
                break
            }
        }
        if empty {
            self.items[0].name = NSLocalizedString("rank1", comment: "rank1")
            self.items[0].qty = 1
            self.items[1].name = NSLocalizedString("rank2", comment: "rank2")
            self.items[1].qty = 3
            self.items[2].name = NSLocalizedString("rank3", comment: "rank3")
            self.items[2].qty = 5
            self.items[3].name = NSLocalizedString("rank4", comment: "rank4")
            self.items[3].qty = 10
            self.items[4].name = NSLocalizedString("rank5", comment: "rank5")
            self.items[4].qty = 20
            self.items[5].name = NSLocalizedString("rank6", comment: "rank6")
            self.items[5].qty = 20
            self.items[6].name = NSLocalizedString("rank7", comment: "rank7")
            self.items[6].qty = 30
            self.saveItems()
        }
    }

    //--------------------------------------------------
    //adMob
    
    struct AdView: UIViewRepresentable {
        func makeUIView(context: Context) -> GADBannerView {
            let banner = GADBannerView(adSize: kGADAdSizeBanner)
            #if DEBUG
            banner.adUnitID = "ca-app-pub-3940256099942544/2934735716"
            #else
            banner.adUnitID = "ca-app-pub-0000000000000000/0000000000"
            #endif
            banner.rootViewController = UIApplication.shared.windows.first?.rootViewController
            banner.load(GADRequest())
            return banner
        }

        func updateUIView(_ uiView: GADBannerView, context: Context) {
        }
    }

    //--------------------------------------------------

}

SettingView.swift

//
//  SettingView.swift
//  LuckyBox
//
//  Created by akira ohmachi on 2021/04/07.
//

import SwiftUI

struct SettingView: View {
    @EnvironmentObject var pub: PublicManager
    
    let inputNameWidth: CGFloat = 0.55

    //init() {
    //    UITableView.appearance().sectionHeaderHeight = .zero
    //    UITableView.appearance().sectionFooterHeight = 10
    //}
    var body: some View {
        VStack(spacing: 0) {
            HStack(spacing: 0) {
                Button(action: {
                    self.pub.isSetting = false
                }) {
                    HStack {
                        Image(systemName: "arrow.backward")
                        Text("cancel")
                    }
                }
                Spacer()
                Button(action: {
                    for i in 0..<self.pub.items.count {
                        self.pub.items[i].name = self.pub.tmpItems[i].name
                        self.pub.items[i].qty = self.adjustStrToNum(str: self.pub.tmpItems[i].qty)
                    }
                    self.pub.saveItems()
                    self.pub.loadItems()
                    self.pub.pin = self.pub.tmpPin
                    self.pub.isSetting = false
                }) {
                    HStack {
                        Text("apply")
                        Image(systemName: "checkmark.circle")
                    }
                }
            }
            .padding(20)
        }
        GeometryReader { bodyView in
            Form {
                Section(header: Text("ticket name / quantity")) {
                    VStack {
                        ForEach(0..<self.pub.items.count) { num in
                            HStack {
                                Text("\(String(format: "%02d",num + 1))").font(.caption2)
                                TextField("", text: self.$pub.tmpItems[num].name)
                                    .textFieldStyle(RoundedBorderTextFieldStyle())
                                    .frame(minWidth: bodyView.size.width * self.inputNameWidth)
                                TextField("", text: Binding(
                                            get: {self.pub.tmpItems[num].qty},
                                            set: {self.pub.tmpItems[num].qty = $0.filter{"0123456789".contains($0)}}))
                                            .textFieldStyle(RoundedBorderTextFieldStyle())
                                            .keyboardType(.numberPad)
                                
                            }
                        }
                    }
                }
                Section(header: Text("pin")) {
                    HStack {
                        Text("pin")
                        TextField("", text: Binding(
                                    get: {self.pub.tmpPin},
                                    set: {self.pub.tmpPin = $0.filter{"0123456789".contains($0)}}))
                                    .textFieldStyle(RoundedBorderTextFieldStyle())
                                    .keyboardType(.numberPad)
                    }
                    Text("pin1")
                        .font(.footnote)
                    Text("pin2")
                        .font(.footnote)
                }
                Section(header: Text("usage")) {
                    Text("usage1")
                        .font(.footnote)
                }
            }
        }
        //.navigationBarTitle("Setting",displayMode: .automatic)
        //.navigationViewStyle(StackNavigationViewStyle())
        .navigationBarHidden(true)
        .onTapGesture {     //背景タップでキーボードを閉じる
            UIApplication.shared.closeKeyboard()
        }

    }
    
    func adjustStrToNum(str: String) -> Int {
        return NumberFormatter().number(from: "0" + str) as! Int
    }

}

extension UIApplication {
    func closeKeyboard() {  //キーボードを閉じる
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

struct SettingView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            SettingView().environmentObject(PublicManager())
        }
    }
}

TextFieldAlertView.swift

//
//  TextFieldAlertView.swift
//  LuckyBox
//
//  Created by akira ohmachi on 2021/04/08.
//

import SwiftUI

struct TextFieldAlertView: UIViewControllerRepresentable {

    @Binding var text: String
    @Binding var isShowingAlert: Bool

    let placeholder: String
    let isSecureTextEntry: Bool
    let title: String
    let message: String

    let leftButtonTitle: String?
    let rightButtonTitle: String?

    var leftButtonAction: (() -> Void)?
    var rightButtonAction: (() -> Void)?

    func makeUIViewController(context: UIViewControllerRepresentableContext<TextFieldAlertView>) -> some UIViewController {
        return UIViewController()
    }

    func updateUIViewController(_ uiViewController: UIViewControllerType, context: UIViewControllerRepresentableContext<TextFieldAlertView>) {

        guard context.coordinator.alert == nil else {
            return
        }

        if !isShowingAlert {
            return
        }

        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        context.coordinator.alert = alert

        alert.addTextField { textField in
            textField.placeholder = placeholder
            textField.text = text
            textField.delegate = context.coordinator
            textField.isSecureTextEntry = isSecureTextEntry
        }

        if leftButtonTitle != nil {
            alert.addAction(UIAlertAction(title: leftButtonTitle, style: .default) { _ in
                alert.dismiss(animated: true) {
                    isShowingAlert = false
                    leftButtonAction?()
                }
            })
        }

        if rightButtonTitle != nil {
            alert.addAction(UIAlertAction(title: rightButtonTitle, style: .default) { _ in
                if let textField = alert.textFields?.first, let text = textField.text {
                    self.text = text
                }
                alert.dismiss(animated: true) {
                    isShowingAlert = false
                    rightButtonAction?()
                }
            })
        }

        DispatchQueue.main.async {
            uiViewController.present(alert, animated: true, completion: {
                isShowingAlert = false
                context.coordinator.alert = nil
            })
        }
    }

    func makeCoordinator() -> TextFieldAlertView.Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UITextFieldDelegate {

        var alert: UIAlertController?
        var view: TextFieldAlertView

        init(_ view: TextFieldAlertView) {
            self.view = view
        }

        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
            if let text = textField.text as NSString? {
                self.view.text = text.replacingCharacters(in: range, with: string)
            } else {
                self.view.text = ""
            }
            return true
        }
    }
}
このページでお気付きの点がございましたら
ご連絡いただけますと幸いです。
Eメール(任意)
送信内容
 

■ 便利ウェブサイト