20200724のSwiftに関する記事は8件です。

Swiftで指定した桁数のランダムな文字列を生成

備忘録:book:
ユニークなIDを生成したいときなどに

func generator(_ length: Int) -> String {
    let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    var randomString = ""
    for _ in 0 ..< length {
        randomString += String(letters.randomElement()!)
    }
    return randomString
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

目でTwitterとブラウザを操作する

この記事は...?

以前、「手を使わずにTwitterを閲覧したい!」という強い意志のもと、NoHandTwitterなるものを作りました。
↓↓↓
https://qiita.com/LilyMameoka/items/52f05eac7ae95b399e32

これが結構使えたんですよ。昼休みに昼食を食べながらTwitterを閲覧できましたし、あとは授業中でもTwitterを...おっと、失言。(開発当時)受験生だっただろ、おい。

で、授業中にTwitterをしていて思ったんですけど、前の記事の状態では先生が来たときにTwitterの画面が見えてしまうんですよね。「これではバレてしまう」、そう思ったんですわ。なんとかしたいですよね。

というわけで、今回は授業中にユースケースを絞り込んで、アップデートしました。

(これを作ったのは前回の記事の直後なのですが、最近「書いてなかったな」と思い出しまして今更ですが、綴りました。)

コードの全貌は記事の最後に掲載しました。

先生が来ちゃったらどーしよ(どーする?!)

まずは先生が来てもTwitterをやっていることがバレないためのアップデートです。授業中の机の上にスマホがある分には怒られませんから、画面を目だけで消しましょうか。

...と、簡単に「画面を目だけで消しましょうか」と言いましたが、コードで画面をロックしてスリープにする方法、調べても出てこないんですよ。コードでスリープさせることはできないんですかね。まぁ、サードパーティー製のよくわからないアプリに画面のスリープやロックを勝手にされるようでは危険ですし、仕方ないかな...。

今回は画面のスリープの代替策として、「画面全体を真っ黒なUIButtonで覆う」ことにしました。

ViewController.swift
    func createBlackButton() {
        blackButton = UIButton()
        blackButton.frame = view.frame
        blackButton.backgroundColor = UIColor.black
        blackButton.addTarget(self, action: #selector(blackButton_TouchUpInside), for: UIControl.Event.touchUpInside)
        view.addSubview(blackButton)
        blackButton.isHidden = true
    }
    @objc func blackButton_TouchUpInside() {
        blackButton.isHidden = true
    }

この関数createBlackButton()で画面全体を真っ黒なUIButtonで覆うもので、これはviewDidLoadで呼び出します。押すと非表示になります。

この「擬似スリープ」は両目を1.5秒以上閉じた際に起動します(非表示が解除される)。前回の記事で、"予め設定した文言をツイートする処理"をしていたところです。

ViewController.swift
    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {

        DispatchQueue.global().async {

//             ~中略~

            if (self.leftEye>self.judgeEyeClose)&&(self.rightEye>self.judgeEyeClose){
                self.closeTime+=1
                if(self.closeTime>self.amountOfFlame){
                    DispatchQueue.main.async{
                        self.blackButton.isHidden = false
                    }
                }
            } else {
                self.closeTime = 0
            }

        }

    }

.isHiddenっていうプロパティ、結構便利ですよね。

これで、授業中先生が巡回してきても目を閉じれば画面は真っ暗。ほら!何もしてませんよ〜〜!

画面をタップすればすぐに復帰できます。(ロックかけていないから、すぐTwitterに復帰できてむしろ良い)

目でブラウザを閲覧したい!

前回の記事ではTwitterの閲覧を主としたアプリにしていました。でも、実際に使ってみて、ブラウザも目で操作したいなぁ、と思いまして。授業中にブラウザで(授業に関連した)調べ物をしている分には(先生にもよるけど大抵は)怒られませんし。

これは簡単です。今まではWKWebViewにTwitterを表示させていたわけですから、読み込むURLをGoogleのトップページあたりにすればよいだけです。

...うーん。それだけではあまり面白くない、ですよね。

というわけで以下の機能を追加しました。

・顎を左に動かして「戻る」, 顎を右に動かして「進む」
・眉を上げるとリロード, 眉を顰めるとGoogleのトップページに戻る

さらに、Twitterもしたいので以下の機能も加えました。

・舌を出したらTwitterとブラウザを切り替える

これらの処理は以下のように実装しています。非常に単純です。Twitterを表示するWKWebViewはブラウザを表示するWKWebViewの上に重ねていて、舌を出すことで表示/非表示を切り替えています。

ViewController.swift
    func twitterSwitch() {
        DispatchQueue.main.async{
            if self.twitterView.isHidden {
                self.twitterView.isHidden = false
            } else {
                self.twitterView.isHidden = true
            }
        }
    }

    func createTwitterView() {
        twitterView = WKWebView(frame:CGRect(x:0, y:0, width:self.view.bounds.size.width, height:self.view.bounds.size.height))
        let urlString = "https://twitter.com/home"
        let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
        let url = NSURL(string: encodedUrlString!)
        let request = NSURLRequest(url: url! as URL)
        twitterView.load(request as URLRequest)
        self.view.addSubview(twitterView)
        twitterView.isHidden = true
    }

    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {

        DispatchQueue.global().async {

            guard let faceAnchor = anchor as? ARFaceAnchor else {
                return
            }

            if let lookUpLeft = faceAnchor.blendShapes[.eyeLookUpLeft] as? Float {
                if lookUpLeft > self.judgeEyeValUp {
                    self.scrollDownInMainThread()
                }
            }
            if let lookUpRight = faceAnchor.blendShapes[.eyeLookUpRight] as? Float {
                if lookUpRight > self.judgeEyeValUp {
                    self.scrollDownInMainThread()
                }
            }
            if let lookDownLeft = faceAnchor.blendShapes[.eyeLookDownLeft] as? Float {
                if lookDownLeft > self.judgeEyeValDown {
                    self.scrollUpInMainThread()
                }
            }
            if let lookDownRight = faceAnchor.blendShapes[.eyeLookDownRight] as? Float {
                if lookDownRight > self.judgeEyeValDown {
                    self.scrollUpInMainThread()
                }
            }

            if let leftEyeBlink = faceAnchor.blendShapes[.eyeBlinkLeft] as? Float{
                self.leftEye = leftEyeBlink
            }
            if let rightEyeBlink = faceAnchor.blendShapes[.eyeBlinkRight] as? Float{
                self.rightEye = rightEyeBlink
            }

            if let browUpAndDown = faceAnchor.blendShapes[.browInnerUp] as? Float{
                self.browVal = browUpAndDown
            }

            if let tongueOut = faceAnchor.blendShapes[.tongueOut] as? Float{
                self.tongueVal = tongueOut
            }

            if let jawLeft = faceAnchor.blendShapes[.jawLeft] as? Float{
                self.jawLeftVal = jawLeft
            }

            if let jawRight = faceAnchor.blendShapes[.jawRight] as? Float{
                self.jawRightVal = jawRight
            }

            if self.browVal < 0.05 {
                let urlString = "https://www.google.co.jp"
                let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
                let url = NSURL(string: encodedUrlString!)
                let request = NSURLRequest(url: url! as URL)
                self.webView.load(request as URLRequest)
            }

            if self.browVal > 0.4 {
                DispatchQueue.main.async{
                    self.webView.reload()
                }
            }

            if self.tongueVal > 0.08 {
                self.twitterSwitch()
            }

            if self.jawLeftVal > 0.1 {
                DispatchQueue.main.async{
                    self.webView.goForward()
                }
            }

            if self.jawRightVal > 0.1 {
                DispatchQueue.main.async{
                    self.webView.goBack()
                }
            }
//             ~中略~

        }

    }

このアプリをハッカソン的な感じの授業で発表するために紹介の動画を作ったのですが、これ使ってる時の顔の動き、到底他人に見せられないな...特にTwitterとブラウザの切り替えが...(その授業のプレゼンではちゃんと見せましたが!!)お嫁に行けない...

ソースコード

AppDelegate.swift、storyboardは何も弄っていません。info.plistも前回の記事のままです(多分)。

ViewController.swift
//
//  ViewController.swift
//  NoHandTwitter
//
//  Created by Lily.Mameoka on 2019/06/19.
//  Copyright © 2019 Lily.Mameoka. All rights reserved.
//

import UIKit
import WebKit
import ARKit

class ViewController: UIViewController, ARSCNViewDelegate, WKUIDelegate{

    private let defaultConfiguration: ARFaceTrackingConfiguration = {
        let configuration = ARFaceTrackingConfiguration()
        return configuration
    }()

    var webView: WKWebView!
    var twitterView: WKWebView!
    var blackButton: UIButton!
    @IBOutlet var sceneView: ARSCNView!

    var judgeEyeValUp:Float = 0.3  //Please adjust!
    var judgeEyeValDown:Float = 0.3  //Please adjust!
    var closeTime:Float = 0
    var amountOfFlame:Float = 15
    var judgeEyeClose:Float = 0.8  //Please adjust!
    var leftEye:Float = 0
    var rightEye:Float = 0
    var browVal:Float = 0.1
    var tongueVal:Float = 0
    var jawLeftVal:Float = 0
    var jawRightVal:Float = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        createWebView()
        createTwitterView()
        createBlackButton()
        sceneView.delegate = self
    }

    func createWebView() {
        webView = WKWebView(frame:CGRect(x:0, y:0, width:self.view.bounds.size.width, height:self.view.bounds.size.height))
        let urlString = "https://www.google.co.jp"
        let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
        let url = NSURL(string: encodedUrlString!)
        let request = NSURLRequest(url: url! as URL)
        webView.load(request as URLRequest)
        self.view.addSubview(webView)
    }

    func createBlackButton() {
        blackButton = UIButton()
        blackButton.frame = view.frame
        blackButton.backgroundColor = UIColor.black
        blackButton.addTarget(self, action: #selector(blackButton_TouchUpInside), for: UIControl.Event.touchUpInside)
        view.addSubview(blackButton)
        blackButton.isHidden = true
    }

    @objc func blackButton_TouchUpInside() {
        blackButton.isHidden = true
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        sceneView.session.run(defaultConfiguration)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        sceneView.session.pause()
    }


    func scrollUpInMainThread() {
        DispatchQueue.main.async {
            if self.twitterView.isHidden {
                self.webView.scrollView.contentOffset = CGPoint(x: 0, y: self.webView.scrollView.contentOffset.y + 10)
            } else {
                self.twitterView.scrollView.contentOffset = CGPoint(x: 0, y: self.twitterView.scrollView.contentOffset.y + 10)
            }

        }
    }

    func scrollDownInMainThread() {
        DispatchQueue.main.async {
            if self.twitterView.isHidden {
                self.webView.scrollView.contentOffset = CGPoint(x: 0, y: self.webView.scrollView.contentOffset.y - 10)
            } else {
                self.twitterView.scrollView.contentOffset = CGPoint(x: 0, y: self.twitterView.scrollView.contentOffset.y - 10)
            }
        }
    }

    func twitterSwitch() {
        DispatchQueue.main.async{
            if self.twitterView.isHidden {
                self.twitterView.isHidden = false
            } else {
                self.twitterView.isHidden = true
            }
        }
    }

    func session(_ session: ARSession, didFailWithError error: Error) {
    }

    func sessionWasInterrupted(_ session: ARSession) {
    }

    func sessionInterruptionEnded(_ session: ARSession) {
    }

    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {

        DispatchQueue.global().async {

            guard let faceAnchor = anchor as? ARFaceAnchor else {
                return
            }

            if let lookUpLeft = faceAnchor.blendShapes[.eyeLookUpLeft] as? Float {
                if lookUpLeft > self.judgeEyeValUp {
                    self.scrollDownInMainThread()
                }
            }
            if let lookUpRight = faceAnchor.blendShapes[.eyeLookUpRight] as? Float {
                if lookUpRight > self.judgeEyeValUp {
                    self.scrollDownInMainThread()
                }
            }
            if let lookDownLeft = faceAnchor.blendShapes[.eyeLookDownLeft] as? Float {
                if lookDownLeft > self.judgeEyeValDown {
                    self.scrollUpInMainThread()
                }
            }
            if let lookDownRight = faceAnchor.blendShapes[.eyeLookDownRight] as? Float {
                if lookDownRight > self.judgeEyeValDown {
                    self.scrollUpInMainThread()
                }
            }

            if let leftEyeBlink = faceAnchor.blendShapes[.eyeBlinkLeft] as? Float{
                self.leftEye = leftEyeBlink
            }
            if let rightEyeBlink = faceAnchor.blendShapes[.eyeBlinkRight] as? Float{
                self.rightEye = rightEyeBlink
            }

            if let browUpAndDown = faceAnchor.blendShapes[.browInnerUp] as? Float{
                self.browVal = browUpAndDown
            }

            if let tongueOut = faceAnchor.blendShapes[.tongueOut] as? Float{
                self.tongueVal = tongueOut
            }

            if let jawLeft = faceAnchor.blendShapes[.jawLeft] as? Float{
                self.jawLeftVal = jawLeft
            }

            if let jawRight = faceAnchor.blendShapes[.jawRight] as? Float{
                self.jawRightVal = jawRight
            }

            if self.browVal < 0.05 {
                let urlString = "https://www.google.co.jp"
                let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
                let url = NSURL(string: encodedUrlString!)
                let request = NSURLRequest(url: url! as URL)
                self.webView.load(request as URLRequest)
            }

            if self.browVal > 0.4 {
                DispatchQueue.main.async{
                    self.webView.reload()
                }
            }

            if self.tongueVal > 0.08 {
                self.twitterSwitch()
            }

            if self.jawLeftVal > 0.1 {
                DispatchQueue.main.async{
                    self.webView.goForward()
                }
            }

            if self.jawRightVal > 0.1 {
                DispatchQueue.main.async{
                    self.webView.goBack()
                }
            }

            if (self.leftEye>self.judgeEyeClose)&&(self.rightEye>self.judgeEyeClose){
                self.closeTime+=1
                if(self.closeTime>self.amountOfFlame){
                    DispatchQueue.main.async{
                        self.blackButton.isHidden = false
                    }
                }
            } else {
                self.closeTime = 0
            }

        }

    }

    func createTwitterView() {
        twitterView = WKWebView(frame:CGRect(x:0, y:0, width:self.view.bounds.size.width, height:self.view.bounds.size.height))
        let urlString = "https://twitter.com/home"
        let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
        let url = NSURL(string: encodedUrlString!)
        let request = NSURLRequest(url: url! as URL)
        twitterView.load(request as URLRequest)
        self.view.addSubview(twitterView)
        twitterView.isHidden = true
    }

}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Twitterの横スワイプメニューのデザインの作り方

はじめに

今回はTwitterアプリの検索タブにあるカテゴリー分けニュースフィードのデザインを紹介します。これはNewsPicksやメルカリでも利用されている、ユーザーエクスペリエンス向上にはもってこいの機能です。ちなみに私の作ったフィットネスアプリ『モニトレ』でもある機能です。

twitter_feed.gif

*ストーリーボードは使用していないので、Extension.swiftファイルに制約のルールを設定しています。(ステップ3をご覧ください)

開発環境

Swift 5.2.4
Xcode 11.5(Deployment Target 13.0)
ストーリーボードなし

ストラクチャー

構成は以下の様になっています。
twitter_feed_structure.png

ステップ1: 初期設定

SceneDelegate.swift内に初期ページ設定をします。初期ページはHomeFeedContentViewとします

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

        // Create the SwiftUI view that provides the window contents.
        let homeFeedContentView = HomeFeedContentView()
        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: homeFeedContentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }

ステップ2: SWiftUIからUICollectionViewControllerを表示する

以下を実装するとUIKitでもホットリロード機能(canvas)が使えるため一々Runしなくてもいいので便利です。

class HomeFeedController: UICollectionViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.backgroundColor = .lightGray
    }
}
struct HomeFeedIntegratedController: UIViewControllerRepresentable {

    func makeUIViewController(context: UIViewControllerRepresentableContext<HomeFeedIntegratedController>) -> HomeFeedController {
        return HomeFeedController(collectionViewLayout: UICollectionViewFlowLayout())
    }

    func updateUIViewController(_ uiViewController: HomeFeedController, context: Context) {

       }
}


struct HomeFeedContentView: View {
    var body: some View {
        HomeFeedIntegratedController().edgesIgnoringSafeArea(.all)
    }
}

struct HomeFeedContentView_Preview: PreviewProvider {
    static var previews: some View {
        HomeFeedContentView()
    }
}

ステップ3: トップメニューバー作り

トップに配置するメニューバーを作成します。

import UIKit
import SwiftUI

protocol TopBarMenuControllerDelegate {
    func didTapMenu(indexPath: IndexPath)
}

class TopBarMenuController: UICollectionViewController, UICollectionViewDelegateFlowLayout {

    var topBarMenuControllerDelegate: TopBarMenuControllerDelegate?

    fileprivate let menuCellId = "menuCellId"
     fileprivate let menuItem = ["おすすめ", "トレンド", "ニュース", "エンタメ"]

    //メニューバー内の青いライン
    let menuBottomLine: UIView = {
        let view = UIView()
        view.backgroundColor = .systemBlue
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.backgroundColor = .darkGray
        collectionView.register(TopBarCell.self, forCellWithReuseIdentifier: menuCellId)
        collectionView.alwaysBounceHorizontal = true

        //横にスクロールするための機能
        if let layout = collectionViewLayout as? UICollectionViewFlowLayout {
            layout.scrollDirection = .horizontal
            layout.minimumLineSpacing = 0
            layout.minimumInteritemSpacing = 0
        }

        view.addSubview(menuBottomLine)
        menuBottomLine.anchor(top: nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, height: 7, width: 0)
        menuBottomLine.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1 / 4).isActive = true
    }

    //menuItem配列の数を返す
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return menuItem.count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: menuCellId, for: indexPath) as! TopBarCell
        cell.menuLabel.text = menuItem[indexPath.item]
        return cell
    }
    //それぞれのセルサイズ(width)はmenuItem配列の数に合わせる = CGSize(width: view.frame.width / 4, height: view.frame.height)
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: view.frame.width / 4, height: view.frame.height)
    }

    //メニューアイテムをタップした時の機能を追加するためにプロトコールを宣言
    override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        topBarMenuControllerDelegate?.didTapMenu(indexPath: indexPath)
    }

}

class TopBarCell: UICollectionViewCell {

    let menuLabel: UILabel = {
        let label = UILabel()
        label.font = .boldSystemFont(ofSize: 16)
        label.textColor = .white
        label.text = "Menu"
        label.textAlignment = .center
        return label
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)

        setupMenuView()
    }

    fileprivate func setupMenuView() {
        addSubview(menuLabel)
        menuLabel.anchor(top: nil, left: nil, bottom: nil, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, height: 20, width: 0)
        menuLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        menuLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

struct TopBarIntegratedController: UIViewControllerRepresentable {

    func makeUIViewController(context: UIViewControllerRepresentableContext<TopBarIntegratedController>) -> TopBarMenuController {
        return TopBarMenuController(collectionViewLayout: UICollectionViewFlowLayout())
    }

    func updateUIViewController(_ uiViewController: TopBarMenuController, context: Context) {

       }
}


struct TopBarMenuContentView: View {
    var body: some View {
        TopBarIntegratedController().edgesIgnoringSafeArea(.all)
    }
}

struct TopBarMenuContentView_Preview: PreviewProvider {
    static var previews: some View {
        TopBarMenuContentView()
    }
}

制約ルールは別ファイルExtensions.swiftで設定しています。

//Extensions.swift
import Foundation
import UIKit

extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?,  bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddingLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, height: CGFloat, width: CGFloat){

    translatesAutoresizingMaskIntoConstraints = false

    if let top = top {
        self.topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
    }

    if let left = left {
        self.leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
    }

    if let bottom = bottom {
        bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
    }

    if let right = right {
        rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
    }

    if width != 0 {
        widthAnchor.constraint(equalToConstant: width).isActive = true
    }

    if height != 0 {
        heightAnchor.constraint(equalToConstant: height).isActive = true
    }
    }
}

ステップ4: メニューアイテムの作成とメニューバーの動き

メニューアイテム[おすすめ、トレンド,ニュース、スポーツ]のテキストと背景の色を指定するためにFeedというクラスを作成。最後にメニューバーの動きを加えます。

import UIKit
import SwiftUI

//メニューアイテムの詳細クラスを作成
class Feed {
    let text: String
    let backgroundColor: UIColor

    init(text: String, backgroundColor: UIColor) {
        self.text = text
        self.backgroundColor = backgroundColor
    }
}

class HomeFeedController: UICollectionViewController, TopBarMenuControllerDelegate, UICollectionViewDelegateFlowLayout {

    fileprivate let menuCellId = "menuCellId"

    var feeds = [Feed]()

    fileprivate let topBarMenuController = TopBarMenuController(collectionViewLayout: UICollectionViewFlowLayout())

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.backgroundColor = .lightGray
        collectionView.register(HomeFeedCell.self, forCellWithReuseIdentifier: menuCellId)

        collectionView.isPagingEnabled = true

        if let layout = collectionViewLayout as? UICollectionViewFlowLayout {
            layout.scrollDirection = .horizontal
            layout.minimumLineSpacing = 0
            layout.minimumInteritemSpacing = 0
        }

        topBarMenuController.topBarMenuControllerDelegate = self

        setupTopBarMenuController()

        feeds = [Feed(text: "おすすめ画面", backgroundColor: .systemRed), Feed(text: "トレンド画面", backgroundColor: .systemTeal), Feed(text: "ニュース画面", backgroundColor: .systemOrange), Feed(text: "エンタメ画面", backgroundColor: .systemGreen)]
    }
    //画面を横にスクロールした時にメニューバー内の下枠(青)が付いてくる仕様
    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let x = scrollView.contentOffset.x
        let offset = x / 4
        topBarMenuController.menuBottomLine.transform = CGAffineTransform(translationX: offset, y: 0)
    }

    override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        let x = targetContentOffset.pointee.x
        let item = x / view.frame.width
        let indexPath = IndexPath(item: Int(item), section: 0)
        topBarMenuController.collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
    }

    //ステップ3で宣言したプロトコールを実装
    //メニューアイテムをタップした時に画面がスライドする機能
    func didTapMenu(indexPath: IndexPath) {
        collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)

    }
    //feeds配列の数を返す
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return feeds.count
    }

    //セルにそれぞれのテキスト背景色を設定する
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: menuCellId, for: indexPath) as! HomeFeedCell
        cell.backgroundColor = feeds[indexPath.item].backgroundColor
        cell.titleLabel.text = feeds[indexPath.item].text
        return cell
    }

    fileprivate func setupTopBarMenuController() {
        //ナビゲーションバーの高さと色を設定
        let navBarController = UINavigationBar(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 72))
        view.addSubview(navBarController)
        navBarController.barTintColor = .darkGray

        //TopBarMenuControllerを設置する
        view.addSubview(topBarMenuController.view)
        topBarMenuController.view.anchor(top: navBarController.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, height: 60, width: 0)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: view.frame.width, height: view.frame.height)
    }
}

class HomeFeedCell: UICollectionViewCell {

    let titleLabel: UILabel = {
        let label = UILabel()
        label.text = "タイトル"
        label.font = .boldSystemFont(ofSize: 16)
        label.textColor = .white
        return label
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)

        addSubview(titleLabel)
        titleLabel.anchor(top: nil, left: nil, bottom: nil, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, height: 0, width: 0)
        titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

struct HomeFeedIntegratedController: UIViewControllerRepresentable {

    func makeUIViewController(context: UIViewControllerRepresentableContext<HomeFeedIntegratedController>) -> HomeFeedController {
        return HomeFeedController(collectionViewLayout: UICollectionViewFlowLayout())
    }

    func updateUIViewController(_ uiViewController: HomeFeedController, context: Context) {

       }
}


struct HomeFeedContentView: View {
    var body: some View {
        HomeFeedIntegratedController().edgesIgnoringSafeArea(.all)
    }
}

struct HomeFeedContentView_Preview: PreviewProvider {
    static var previews: some View {
        HomeFeedContentView()
    }
}

最後に

今回はデザインのみですが、実際にはカテゴリー別の情報(APIなど)ををそれぞれのページに表示させます。
次の記事はそれを実装しようと思いますが、何がいいですかね?
何かリクエストがあればお気軽にコメントからどうぞ!

現在、私の制作した体験/遊び/学びが楽しめるアプリ『WalCal』でジャンケンアプリ制作体験を掲載しています。

ご興味ある方は是非チェックして見てください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift初心者がJSON解析をやってみる

JSON解析ってかっこいい響き!

JSON解析って言葉かっこいい。賢そう!
APIとかもよく分からない。そんなswift歴1週間の私がSwiftを使ってJSON解析をやりますよ〜
ここではpixabayってサイトから画像を取ってきます。

pixabayでAPIの仕様書を確認しよう。

pixabay←こちらどす
サインアップする必要があります。

image.png

右上のExploreってとこからAPIってとこ押すと、

image.png

こんなのが出るからGetStartedを押しちゃう

スクリーンショット 2020-07-24 18.58.22.png
ちょっとしたにスクロールするとkeyと書かれているのがある。APIを叩く時にはここのkeyを指定してアクセスする必要がある。

image.png

APIを叩いて返ってくるJSONはこんな感じ。13行目のWebFormatURLがお目当ての画像のURL。こいつをもらうよ!

準備からはじめよう

まずは準備から。cocoapodというものをインストールする。
https://qiita.com/ShinokiRyosei/items/3090290cb72434852460
そして、Alamofire, SwiftyJSON, SDWebImageをcocoaposdを使ってインストールしましょう。

じゃあ実際にコード書こうね。

//画像を扱うのもあってこいつらをインポートするぜ!
import UIKit
import Alamofire
import SwiftyJSON
import SDWebImage
import Photos

class ViewController: UIViewController {
    //テキストフィールド。ここに入れたキーワードに関する画像を取得するぜ!
    @IBOutlet weak var searchTextField: UITextField!
    //画像を表示するイメージビュー
    @IBOutlet weak var imageView: UIImageView!

    //テキストフィールドに文言を入れてボタンをポチッとやった時。入力された文言で検索した画像を取得するよん
    @IBAction func searchAction(_ sender: Any) {
        if searchTextField.text == "" {
            //何も文言が入っていなかった時、一旦dogで検索するよ。
            getImages(keyword: "dog")
        } else {
            //文言が入っていたらそのキーワードで画像を取得してくるわ!
            getImages(keyword: searchTextField.text!)
        }    
    }

    //検索キーワードをもとに画像を取得するメソッドを読んでる。
    func getImages(keyword: String) {
        //ここにアクセスしたら画像あげるよ!!っていうURLをここに書く。sampleって書いてあるところはkeyで置き換えてくれよな!
        let url = "https://pixabay.com/api/?key=sample=\(keyword)"
        //Alamofireを使ってAPIリクエストを出す。Alamofireについては詳しくは知らない。
        //下のAFはAlmofireの略。このurlに対してGETメソッドでJSON形式でリクエストするで!っていってる(多分)。そんで返ってきたレスポンスに対する処遇をクロージャーで記述する!
        AF.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON {(response) in
            switch(response.result) {
            //API通信成功した時〜
            case.success:
                //返ってきたJSONを変数にぶち込むで!
                let json:JSON = JSON(response.data as Any)
                //JSONの中から欲しい情報だけ抽出
                var imageString = json["hits"][0]["webformatURL"].string
                //画面に表示しまっせ。
                self.imageView.sd_setImage(with: URL(string: imageString!), completed: nil)
            //失敗した時〜
            case.failure(let error):
                //失敗したらエラー文が返ってくるのでそれを表示しちゃう。
                print(error)
            }
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Realmを入れるとビルドエラー(CocoaPods)

エラー

CocoaPodsでRealmを入れてアプリ作っていると、ビルドエラーが体感で10回中8回以上発生。

スクリーンショット 2020-07-24 16.24.45.png

解決方法

ターミナルにこちらのコマンドを投下!

rm -rf ~/Library/Caches/CocoaPods/Pods/Release/Realm ~/Library/Caches/CocoaPods/Pods/Specs/Release/Realm

キャッシュが削除されて、問題なくビルドできるようになりました!イェイ!

参考

https://github.com/realm/realm-cocoa/issues/6407

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TextFieldでのViewがキーボードで隠れなくなりました!

初めに

 先日、Big Sur Beta3へのUpDateがありましたね!それに伴い、Xcode12 beta3の更新もありました。Big Surは、まだまだバグが多いですが、個人的にUIや使用感が好きなので、私はCatalinaにダウングレードせず使用し続けています。

本文

 今回のbeta3へのUpDateで、SwiftUIでのTextFieldの文字入力の際に、自動的にキーボードがViewを回避するようになりました!個人的にめちゃくちゃ嬉しいので記事を書かせていただきました!!

txtfield.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSアプリ マルチデバイス対応をコピペでやるよ

マルチデバイス対応

端末によって画面サイズが異なるので端末に合わせた対応が必要。
その方法の1つをメモっておこ〜

SceneDelegate.swiftに以下をコピペしよう

SceneDelegate.swiftってファイルがデフォルトであるはずなので、そこのsceneメソッドのところに以下をコピペしましょ。
これをコピペしただけだとエラーが出るけど気にしないでおk。

//端末の画面の大きさを取得
let storyboard:UIStoryboard = self.grabStoryboard()
//表示するstoryBoardをセット!
if let window = window{
    window.rootViewController = storyboard.instantiateInitialViewController() as UIViewController?
    }
self.window?.makeKeyAndVisible()

次に、SceneDelegate.swiftの空いているところに以下をコピペしましょ。

//使用している端末は何かな?チェック!
func grabStoryboard() -> UIStoryboard{
            var storyboard = UIStoryboard()
            let height = UIScreen.main.bounds.size.height
            //画面の高さが667pxってことは、さてはiPhoneSE2ですかな??
            if height == 667 {
                storyboard = UIStoryboard(name: "iPhoneSE2", bundle: nil)
            //画面の高さが736pxってことは、さてはiPhone8plusですかな??
            }else if height == 736 {
                storyboard = UIStoryboard(name: "iPhone8plus", bundle: nil)
            //上のコメントと一緒な感じだよ。察してくれよな!
            }else if height == 812{
                storyboard = UIStoryboard(name: "iPhone11Pro", bundle: nil)
            //以下略
            }else if height == 896{
                storyboard = UIStoryboard(name: "iPhone11ProMax", bundle: nil)
            }else if height == 1112{ 
                storyboard = UIStoryboard(name: "iPad", bundle: nil)
            }else{
                switch UIDevice.current.model {
                case "iPnone" :
                storyboard = UIStoryboard(name: "se", bundle: nil)
                    break
                case "iPad" :
                storyboard = UIStoryboard(name: "iPad", bundle: nil)
                print("iPad")
                    break
                default:
                    break
                }
            }
            return storyboard
    }

storyboardを作るよ〜

次にstoryBordを対応する機種だけ作っておきます〜
新しいstoryBoardを作る → ファイル名を対応したい機種の名前に!(さっきコピペしたgrabStoryboardメソッドに示されている機種名だけ対応可)
そして、xcodeの下の方の「view as: iPhone SE(2nd generation)」とか書いてあるところをタップ

image.png

すると以下のような表示が出るから、ファイル名に指定した画面を選択

image.png

次に「Is initial controller」にチェックを入れる〜
スクリーンショット 2020-07-24 13.34.46.png

これを対応したい機種の種類分作成します。
これで完了!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

3年くらい放置していたiOSアプリをビルドした時にやったこと。

はじめに

3年くらい放置していたアプリのメンテナンスが必要になり、ビルドをしたらぜんぜんダメだったのですが、解決方法を記録しておきます。
主にダメだったのは以下の点です

  • Swiftのバージョン
  • UITableView
  • UICollectionView

開発環境

  • 対応前 Xcode9
  • 対応後 Xcode11.5

対応内容

SWIFT_VERSIONエラー

エラ〜メッセージ
SWIFT_VERSION '3.0' is unsupported, supported versions are: 4.0, 4.2, 5.0.

もともとSwift3.0で書かれていたのですが、Xcode11ではすでにSwiftのバージョンは5.0になっています。しかしバージョン2つ飛ばしは心配なので、4.0を選択しました。
幸いにもSwift3.0から4.0の差分はそれほどないので、バージョンの変更だけですみました。
Build SettingsのSwift Language Versionを4.0に変更しました。

変更前

スクリーンショット 2020-07-23 18.04.27.png

変更後

スクリーンショット 2020-07-23 18.07.09.png

同様に、Podfileにもバージョン指定があったので、削除しました

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['SWIFT_VERSION'] = '3.0'
    end
  end
end

UITableViewやUICollectionViewのDelegateが変更されていた

次に発生したのは以下のエラー

[xxxx.StoryViewController tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x7fc48663aa50
[xxxx.StoryViewController tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x7fc48663aa50'

UITabeViewDelegateとUITableViewDatasourceのメソッド名が変更されていました。
StoryBoardでDelegateをつなげるとProtocolを書かなくても問題なく動作していたのですが、代わりにコンパイル時にエラーチェックされないので、ちゃんとプロトコルを定義するように変更しました。
Protocolを追加しておけばXcodeの機能で自動的に変換できます。

修正前

class StoryViewController: UIViewController {
     :

修正後

class StoryViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
     :

Protocolを設定すると、以下のようにエラーメッセージが表示され、Fixボタンを押すと自動的に変換されます。
スクリーンショット 2020-07-24 9.08.56.png

最後に

分かってしまえば大したことのない修正項目ですが、これだけでも数時間費やしてしまいました。同じことにハマる人も多いと思いますので、(そんなにいないかな)念のため共有しておきます。
SwiftやiOS SDKは毎年変化があり、毎年メンテナンスしていればこのような問題はそれほど起きないのですが、バージョンを飛ばすと途端に対応が大変になったりしますね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む