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

ソースコード source code

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

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

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

ConstValue.swift

//
//  ConstValue.swift
//  FortuneSlip
//
//  Created by akira ohmachi on 2021/04/06.
//

import SwiftUI

class ConstValue: ObservableObject {

    //background
    static let colorBg: Color = Color.init(red: 254 / 255, green: 0, blue: 0)
    
    //button color
    static let colorStart: Color = Color.init(red: 254 / 255, green: 193 / 255, blue: 7 / 255)
    static let colorSetting: Color = Color.init(red: 255 / 256, green: 152 / 255, blue: 1 / 255)
}

ContentView.swift

//
//  ContentView.swift
//  FortuneSlip
//
//  Created by akira ohmachi on 2021/04/06.
//

import SwiftUI
import GoogleMobileAds

struct ContentView : View {
    @EnvironmentObject var pub: PublicManager
    @State private var busyFlag: Bool = false
    @State private var destroyFlag: Bool = false
    @State private var omikujiImageName: String = "omikuji001"
    @State private var timeLine: Int = 0
    @State private var resultTextName: String = ""
    @State private var resultTextAlpha: Double = 0.0
    @State private var resultTextPosition: CGPoint = CGPoint(x: 0, y: 0)
    
    struct ResultTextPosition {
        let time: Int
        let x: Int
        let y: Int
        let alpha: Double
        init(time: Int, x: Int, y: Int, alpha: Double) {
            self.time = time
            self.x = x
            self.y = y
            self.alpha = alpha
        }
    }

    private var resultTextPositions: Array<ResultTextPosition> = [
        ResultTextPosition(time: 84, x: 547, y: 440, alpha: 0.00),
        ResultTextPosition(time: 85, x: 549, y: 444, alpha: 0.03),
        ResultTextPosition(time: 86, x: 550, y: 450, alpha: 0.06),
        ResultTextPosition(time: 87, x: 550, y: 454, alpha: 0.09),
        ResultTextPosition(time: 88, x: 552, y: 459, alpha: 0.11),
        ResultTextPosition(time: 89, x: 554, y: 464, alpha: 0.14),
        ResultTextPosition(time: 90, x: 555, y: 469, alpha: 0.17),
        ResultTextPosition(time: 91, x: 556, y: 474, alpha: 0.20),
        ResultTextPosition(time: 92, x: 558, y: 479, alpha: 0.23),
        ResultTextPosition(time: 93, x: 560, y: 484, alpha: 0.26),
        ResultTextPosition(time: 94, x: 562, y: 489, alpha: 0.29),
        ResultTextPosition(time: 95, x: 563, y: 495, alpha: 0.31),
        ResultTextPosition(time: 96, x: 565, y: 500, alpha: 0.34),
        ResultTextPosition(time: 97, x: 567, y: 505, alpha: 0.37),
        ResultTextPosition(time: 98, x: 569, y: 511, alpha: 0.40),
        ResultTextPosition(time: 99, x: 571, y: 516, alpha: 0.43),
        ResultTextPosition(time: 100, x: 573, y: 522, alpha: 0.46),
        ResultTextPosition(time: 101, x: 575, y: 530, alpha: 0.49),
        ResultTextPosition(time: 102, x: 578, y: 539, alpha: 0.51),
        ResultTextPosition(time: 103, x: 580, y: 550, alpha: 0.54),
        ResultTextPosition(time: 104, x: 583, y: 563, alpha: 0.57),
        ResultTextPosition(time: 105, x: 587, y: 576, alpha: 0.60),
        ResultTextPosition(time: 106, x: 590, y: 592, alpha: 0.63),
        ResultTextPosition(time: 107, x: 594, y: 608, alpha: 0.66),
        ResultTextPosition(time: 108, x: 599, y: 626, alpha: 0.69),
        ResultTextPosition(time: 109, x: 604, y: 645, alpha: 0.71),
        ResultTextPosition(time: 110, x: 608, y: 665, alpha: 0.74),
        ResultTextPosition(time: 111, x: 614, y: 687, alpha: 0.77),
        ResultTextPosition(time: 112, x: 619, y: 708, alpha: 0.80),
        ResultTextPosition(time: 113, x: 625, y: 730, alpha: 0.83),
        ResultTextPosition(time: 114, x: 630, y: 751, alpha: 0.86),
        ResultTextPosition(time: 115, x: 636, y: 772, alpha: 0.89),
        ResultTextPosition(time: 116, x: 641, y: 792, alpha: 0.91),
        ResultTextPosition(time: 117, x: 646, y: 808, alpha: 0.94),
        ResultTextPosition(time: 118, x: 651, y: 822, alpha: 0.97),
        ResultTextPosition(time: 119, x: 654, y: 832, alpha: 1.00),
    ]

    var body: some View {
        NavigationView{
            GeometryReader { bodyView in
                VStack(spacing: 0) {
                    HStack(spacing: 0) {
                        Button(action:{
                            self.omikujiStart()
                        }){
                            Text("start")
                                .frame(width: bodyView.size.width / 4 * 3, height: bodyView.size.height / 6, alignment: .center)
                                .background(ConstValue.colorStart)
                                .foregroundColor(.primary)
                        }
                        Rectangle().fill(Color.white).frame(width: 1,height: bodyView.size.height / 6)
                        NavigationLink(destination: SettingView(),isActive: self.$pub.isSetting) {
                            Button(action: {
                                self.buttonActionSetting()
                            }){
                                Text("setting")
                                    .frame(width: bodyView.size.width / 4, height: bodyView.size.height / 6, alignment: .center)
                                    .background(ConstValue.colorSetting)
                                    .foregroundColor(.primary)
                            }
                        }
                        .background(ConstValue.colorBg)
                    }
                    .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 {
                            Spacer(minLength: 20)
                            ZStack {
                                Image(self.omikujiImageName)
                                    .resizable()
                                    .frame(width: bodyView.size.width, height: bodyView.size.width, alignment: .center)
                                self.omikujiText(size: bodyView.size.width)
                            }
                        }
                        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)
            .navigationBarHidden(true)
        }
        .navigationViewStyle(StackNavigationViewStyle())
        .onDisappear {     //アプリ終了時
            self.destroyFlag = true
        }
    }
    
    private func omikujiText(size: CGFloat) -> some View {
        let offsetX: CGFloat = size / 900.0 * self.resultTextPosition.x - (size / 2) - 20
        let offsetY: CGFloat = size / 900.0 * self.resultTextPosition.y - (size / 2) - 20
        var body : some View {
            Text(self.resultTextName)
                .foregroundColor(Color.white)
                .font(.title)
                .opacity(self.resultTextAlpha)
                .offset(CGSize(width: offsetX, height: offsetY))
                .animation(.none)
            }
        return body
    }

    private func initLotteryItems() {
        if self.pub.lotteryItem1 == ""
            && self.pub.lotteryItem2 == ""
            && self.pub.lotteryItem3 == ""
            && self.pub.lotteryItem4 == ""
            && self.pub.lotteryItem5 == ""
            && self.pub.lotteryItem6 == ""
            && self.pub.lotteryItem7 == ""
        {
            self.pub.qtyItem1 = "1"
            self.pub.qtyItem2 = "3"
            self.pub.qtyItem3 = "5"
            self.pub.qtyItem4 = "5"
            self.pub.qtyItem5 = "5"
            self.pub.qtyItem6 = "1"
            self.pub.qtyItem7 = "1"
            self.pub.lotteryItem1 = NSLocalizedString("rank1" , comment : "rank1")
            self.pub.lotteryItem2 = NSLocalizedString("rank2" , comment : "rank2")
            self.pub.lotteryItem3 = NSLocalizedString("rank3" , comment : "rank3")
            self.pub.lotteryItem4 = NSLocalizedString("rank4" , comment : "rank4")
            self.pub.lotteryItem5 = NSLocalizedString("rank5" , comment : "rank5")
            self.pub.lotteryItem6 = NSLocalizedString("rank6" , comment : "rank6")
            self.pub.lotteryItem7 = NSLocalizedString("rank7" , comment : "rank7")
        }
    }
    
    private func buttonActionSetting() {
        if self.busyFlag {
            return
        }
        self.initLotteryItems()
        self.pub.tmpLotteryItem1 = self.pub.lotteryItem1
        self.pub.tmpLotteryItem2 = self.pub.lotteryItem2
        self.pub.tmpLotteryItem3 = self.pub.lotteryItem3
        self.pub.tmpLotteryItem4 = self.pub.lotteryItem4
        self.pub.tmpLotteryItem5 = self.pub.lotteryItem5
        self.pub.tmpLotteryItem6 = self.pub.lotteryItem6
        self.pub.tmpLotteryItem7 = self.pub.lotteryItem7
        self.pub.tmpQtyItem1 = self.pub.qtyItem1
        self.pub.tmpQtyItem2 = self.pub.qtyItem2
        self.pub.tmpQtyItem3 = self.pub.qtyItem3
        self.pub.tmpQtyItem4 = self.pub.qtyItem4
        self.pub.tmpQtyItem5 = self.pub.qtyItem5
        self.pub.tmpQtyItem6 = self.pub.qtyItem6
        self.pub.tmpQtyItem7 = self.pub.qtyItem7
        self.pub.isSetting = true
    }
    
    private func setImageName() {
        self.omikujiImageName = "omikuji" + String(format: "%03d", self.timeLine)
    }
    
    private func omikujiMove() {
        if self.timeLine < 84 || self.timeLine > 119 {
            return
        }
        for i in 0..<self.resultTextPositions.count {
            if self.resultTextPositions[i].time == self.timeLine {
                self.resultTextPosition = CGPoint(x: self.resultTextPositions[i].x, y: self.resultTextPositions[i].y)
	            self.resultTextAlpha = self.resultTextPositions[i].alpha
                break
            }
        }
    }
    
    private func omikujiStart() {
        if self.busyFlag {
            return
        }
        self.busyFlag = true
        self.initLotteryItems()
        self.resultTextName = ""
        self.resultTextAlpha = 0.0
        let qtys: Array<Int> = [
            NumberFormatter().number(from: "0" + self.pub.qtyItem1) as! Int,
            NumberFormatter().number(from: "0" + self.pub.qtyItem2) as! Int,
            NumberFormatter().number(from: "0" + self.pub.qtyItem3) as! Int,
            NumberFormatter().number(from: "0" + self.pub.qtyItem4) as! Int,
            NumberFormatter().number(from: "0" + self.pub.qtyItem5) as! Int,
            NumberFormatter().number(from: "0" + self.pub.qtyItem6) as! Int,
            NumberFormatter().number(from: "0" + self.pub.qtyItem7) as! Int,
        ]
        var omikujiSum: Int = 0
        for i in 0 ..< qtys.count {
            omikujiSum += qtys[i]
        }
        var remainNum: Int = Int.random(in: 0 ..< omikujiSum)
        var choiceLottery: Int = -1
        for i in 0 ..< qtys.count {
            if qtys[i] != 0 {
                if qtys[i] <= remainNum {
                    remainNum -= qtys[i]
                } else {
                    choiceLottery = i
                    break
                }
            }
        }
        switch choiceLottery {
        case 0: self.resultTextName = self.pub.lotteryItem1
        case 1: self.resultTextName = self.pub.lotteryItem2
        case 2: self.resultTextName = self.pub.lotteryItem3
        case 3: self.resultTextName = self.pub.lotteryItem4
        case 4: self.resultTextName = self.pub.lotteryItem5
        case 5: self.resultTextName = self.pub.lotteryItem6
        case 6: self.resultTextName = self.pub.lotteryItem7
        default: break
        }
        self.timeLine = 0
        self.timeLineAnimation()
    }

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

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

FortuneSlipApp.swift

//
//  FortuneSlipApp.swift
//  FortuneSlip
//
//  Created by akira ohmachi on 2021/04/06.
//

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 FortuneSlipApp: App {
    // SwiftUI AppライフサイクルにAppDelegateクラスを注入する
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

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

PublicManager.swift

//
//  PublicManager.swift
//  FortuneSlip
//
//  Created by akira ohmachi on 2021/04/06.
//

import SwiftUI
import GoogleMobileAds

class PublicManager: ObservableObject {

    @Published var isSetting: Bool = false

    @AppStorage("lotteryItem1") var lotteryItem1: String = ""
    @AppStorage("lotteryItem2") var lotteryItem2: String = ""
    @AppStorage("lotteryItem3") var lotteryItem3: String = ""
    @AppStorage("lotteryItem4") var lotteryItem4: String = ""
    @AppStorage("lotteryItem5") var lotteryItem5: String = ""
    @AppStorage("lotteryItem6") var lotteryItem6: String = ""
    @AppStorage("lotteryItem7") var lotteryItem7: String = ""

    @AppStorage("qtyItem1") var qtyItem1: String = ""
    @AppStorage("qtyItem2") var qtyItem2: String = ""
    @AppStorage("qtyItem3") var qtyItem3: String = ""
    @AppStorage("qtyItem4") var qtyItem4: String = ""
    @AppStorage("qtyItem5") var qtyItem5: String = ""
    @AppStorage("qtyItem6") var qtyItem6: String = ""
    @AppStorage("qtyItem7") var qtyItem7: String = ""

    @Published var tmpLotteryItem1: String = ""
    @Published var tmpLotteryItem2: String = ""
    @Published var tmpLotteryItem3: String = ""
    @Published var tmpLotteryItem4: String = ""
    @Published var tmpLotteryItem5: String = ""
    @Published var tmpLotteryItem6: String = ""
    @Published var tmpLotteryItem7: String = ""

    @Published var tmpQtyItem1: String = ""
    @Published var tmpQtyItem2: String = ""
    @Published var tmpQtyItem3: String = ""
    @Published var tmpQtyItem4: String = ""
    @Published var tmpQtyItem5: String = ""
    @Published var tmpQtyItem6: String = ""
    @Published var tmpQtyItem7: String = ""

    //--------------------------------------------------
    //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
//  FortuneSlip
//
//  Created by akira ohmachi on 2021/04/06.
//

import SwiftUI

struct SettingView: View {
    @EnvironmentObject var pub: PublicManager

    let inputNameWidth: CGFloat = 0.65

    var body: some View {
        VStack() {
            HStack {
                Button(action: {
                    self.pub.isSetting = false
                }) {
                    HStack {
                        Image(systemName: "arrow.backward")
                        Text("cancel")
                    }
                }
                Spacer()
                Button(action : {
                    self.pub.lotteryItem1 = self.pub.tmpLotteryItem1
                    self.pub.lotteryItem2 = self.pub.tmpLotteryItem2
                    self.pub.lotteryItem3 = self.pub.tmpLotteryItem3
                    self.pub.lotteryItem4 = self.pub.tmpLotteryItem4
                    self.pub.lotteryItem5 = self.pub.tmpLotteryItem5
                    self.pub.lotteryItem6 = self.pub.tmpLotteryItem6
                    self.pub.lotteryItem7 = self.pub.tmpLotteryItem7
                    self.pub.qtyItem1 = self.adjustStrNum(str : pub.tmpQtyItem1)
                    self.pub.qtyItem2 = self.adjustStrNum(str : pub.tmpQtyItem2)
                    self.pub.qtyItem3 = self.adjustStrNum(str : pub.tmpQtyItem3)
                    self.pub.qtyItem4 = self.adjustStrNum(str : pub.tmpQtyItem4)
                    self.pub.qtyItem5 = self.adjustStrNum(str : pub.tmpQtyItem5)
                    self.pub.qtyItem6 = self.adjustStrNum(str : pub.tmpQtyItem6)
                    self.pub.qtyItem7 = self.adjustStrNum(str : pub.tmpQtyItem7)
                    self.pub.isSetting = false
                }) {
                    HStack {
                        Text("apply")
                        Image(systemName: "checkmark.circle")
                    }
                }
            }
            .padding(20)
        }
        GeometryReader { bodyView in
            Form {
                Section(header: Text("1: " + NSLocalizedString("lottery / quantity", comment: "lottery / quantity"))) {
                    HStack {
                        TextField("", text : self.$pub.tmpLotteryItem1)
                                    .textFieldStyle(RoundedBorderTextFieldStyle())
                                    .frame(minWidth: bodyView.size.width * self.inputNameWidth)
                        TextField("", text : Binding(
                                    get: {self.pub.tmpQtyItem1} ,
                                    set: {self.pub.tmpQtyItem1 = $0.filter{"0123456789".contains($0)}})
                        )
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .keyboardType(.numberPad)
                    }
                }
                Section(header: Text("2: " + NSLocalizedString("lottery / quantity", comment: "lottery / quantity"))) {
                    HStack {
                        TextField("", text : self.$pub.tmpLotteryItem2)
                                    .textFieldStyle(RoundedBorderTextFieldStyle())
                                    .frame(minWidth: bodyView.size.width * self.inputNameWidth)
                        TextField("", text : Binding(
                                    get: {self.pub.tmpQtyItem2} ,
                                    set: {self.pub.tmpQtyItem2 = $0.filter{"0123456789".contains($0)}})
                        )
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .keyboardType(.numberPad)
                    }
                }
                Section(header: Text("3: " + NSLocalizedString("lottery / quantity", comment: "lottery / quantity"))) {
                    HStack {
                        TextField("", text : self.$pub.tmpLotteryItem3)
                                    .textFieldStyle(RoundedBorderTextFieldStyle())
                                    .frame(minWidth: bodyView.size.width * self.inputNameWidth)
                        TextField("", text : Binding(
                                    get: {self.pub.tmpQtyItem3} ,
                                    set: {self.pub.tmpQtyItem3 = $0.filter{"0123456789".contains($0)}})
                        )
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .keyboardType(.numberPad)
                    }
                }
                Section(header: Text("4: " + NSLocalizedString("lottery / quantity", comment: "lottery / quantity"))) {
                    HStack {
                        TextField("", text : self.$pub.tmpLotteryItem4)
                                    .textFieldStyle(RoundedBorderTextFieldStyle())
                                    .frame(minWidth: bodyView.size.width * self.inputNameWidth)
                        TextField("", text : Binding(
                                    get: {self.pub.tmpQtyItem4} ,
                                    set: {self.pub.tmpQtyItem4 = $0.filter{"0123456789".contains($0)}})
                        )
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .keyboardType(.numberPad)
                    }
                }
                Section(header: Text("5: " + NSLocalizedString("lottery / quantity", comment: "lottery / quantity"))) {
                    HStack {
                        TextField("", text : self.$pub.tmpLotteryItem5)
                                    .textFieldStyle(RoundedBorderTextFieldStyle())
                                    .frame(minWidth: bodyView.size.width * self.inputNameWidth)
                        TextField("", text : Binding(
                                    get: {self.pub.tmpQtyItem5} ,
                                    set: {self.pub.tmpQtyItem5 = $0.filter{"0123456789".contains($0)}})
                        )
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .keyboardType(.numberPad)
                    }
                }
                Section(header: Text("6: " + NSLocalizedString("lottery / quantity", comment: "lottery / quantity"))) {
                    HStack {
                        TextField("", text : self.$pub.tmpLotteryItem6)
                                    .textFieldStyle(RoundedBorderTextFieldStyle())
                                    .frame(minWidth: bodyView.size.width * self.inputNameWidth)
                        TextField("", text : Binding(
                                    get: {self.pub.tmpQtyItem6} ,
                                    set: {self.pub.tmpQtyItem6 = $0.filter{"0123456789".contains($0)}})
                        )
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .keyboardType(.numberPad)
                    }
                }
                Section(header: Text("7: " + NSLocalizedString("lottery / quantity", comment: "lottery / quantity"))) {
                    HStack {
                        TextField("", text : self.$pub.tmpLotteryItem7)
                                    .textFieldStyle(RoundedBorderTextFieldStyle())
                                    .frame(minWidth: bodyView.size.width * self.inputNameWidth)
                        TextField("", text : Binding(
                                    get: {self.pub.tmpQtyItem7} ,
                                    set: {self.pub.tmpQtyItem7 = $0.filter{"0123456789".contains($0)}})
                        )
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .keyboardType(.numberPad)
                    }
                }
                Section(header: Text("usage")) {
                    Text("usage1")
                        .font(.footnote)
                }
            }
        }
        .navigationBarHidden(true)
        .onTapGesture {     //背景タップでキーボードを閉じる
            UIApplication.shared.closeKeyboard()
        }
    }

    func adjustStrNum(str : String) -> String {
        let num : Int = NumberFormatter().number(from : "0" + str) as! Int
        if num == 0 {
            return ""
        }
        return String(num)
    }
}

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())
        }
    }
}
このページでお気付きの点がございましたら
ご連絡いただけますと幸いです。
Eメール(任意)
送信内容
 

■ 便利ウェブサイト