Rodhos Soft

備忘録を兼ねた技術的なメモです。Rofhos SoftではiOSアプリ開発を中心としてAndroid, Webサービス等の開発を承っております。まずはご相談下さい。

pdfをepsにする

ひとまずこんな感じでやってみた。

#!/bin/bash

# echo "$1"
# 拡張子を除く
BASE_FILE_NAME=`basename "$1" .pdf`
# echo $BASE_FILE_NAME

pdf2ps "$1"
ps2eps ${BASE_FILE_NAME}.ps

bashの参考

Bashの便利な構文だがよく忘れてしまうものの備忘録 - Qiita

bashで拡張子を除いたファイル名を取得する - Qiita

Stackの中のカスタムビュー

Stack内でカスタムで作ったViewが縮む問題、以下で解決できた。

                NSLayoutConstraint.activate(
                    view.widthAnchor.constraint(equalToConstant: 320),
                    view.heightAnchor.constraint(equalToConstant: 320)
                )

スキーム設定

スキーム追加

Product->Edit Schemeでduplicate

Config追加

project file, Project, InfoタグのConfigurationで追加 スキームのエディットでrunを追加したConfigに設定

プリプロセッサ定義

prroject fileのBuild settingのPreprocessor Macroにそれぞれのコンフィグで定義したいマクロ変数を追加、MOCK=1等 また、Swiftから使えるように Other Swift Flagsに-DMOCKなどのように-D{名前}を入れる。

定義したものを使う

struct Const {
    static var apiBase: String {
        #if MOCK
            return "http://hoge.local:3000/api/v1/"
        #elseif DEBUG
            return "http://hogedebug.local:3000/api/v1/"
        #else
            return "https://hoge.com:3000/api/v2/"
        #endif
    }
    
    static var isMock: Bool {
        #if MOCK
            return true
        #else
            return false
        #endif
    }
}

フォント指定

stackoverflow.com

指定できるフォントを調べるには

        for family in UIFont.familyNames.sorted() {
            let names = UIFont.fontNames(forFamilyName: family)
            print("Family: \(family) Font names: \(names)")
        }

フォントの指定は

                Text("acccdefああ投稿").font(.custom("CourierNewPS-ItalicMT", size: 20))

のようにする。

先程のフォント名一覧はこのようにでた。

Family: Academy Engraved LET Font names: ["AcademyEngravedLetPlain"]
Family: Al Nile Font names: ["AlNile", "AlNile-Bold"]
Family: American Typewriter Font names: ["AmericanTypewriter-CondensedBold", "AmericanTypewriter-Condensed", "AmericanTypewriter-CondensedLight", "AmericanTypewriter", "AmericanTypewriter-Bold", "AmericanTypewriter-Semibold", "AmericanTypewriter-Light"]
Family: Apple Color Emoji Font names: ["AppleColorEmoji"]
Family: Apple SD Gothic Neo Font names: ["AppleSDGothicNeo-Thin", "AppleSDGothicNeo-Light", "AppleSDGothicNeo-Regular", "AppleSDGothicNeo-Bold", "AppleSDGothicNeo-SemiBold", "AppleSDGothicNeo-UltraLight", "AppleSDGothicNeo-Medium"]
Family: Apple Symbols Font names: ["AppleSymbols"]
Family: Arial Font names: ["Arial-BoldMT", "Arial-BoldItalicMT", "Arial-ItalicMT", "ArialMT"]
Family: Arial Hebrew Font names: ["ArialHebrew-Bold", "ArialHebrew-Light", "ArialHebrew"]
Family: Arial Rounded MT Bold Font names: ["ArialRoundedMTBold"]
Family: Avenir Font names: ["Avenir-Oblique", "Avenir-HeavyOblique", "Avenir-Heavy", "Avenir-BlackOblique", "Avenir-BookOblique", "Avenir-Roman", "Avenir-Medium", "Avenir-Black", "Avenir-Light", "Avenir-MediumOblique", "Avenir-Book", "Avenir-LightOblique"]
Family: Avenir Next Font names: ["AvenirNext-Medium", "AvenirNext-DemiBoldItalic", "AvenirNext-DemiBold", "AvenirNext-HeavyItalic", "AvenirNext-Regular", "AvenirNext-Italic", "AvenirNext-MediumItalic", "AvenirNext-UltraLightItalic", "AvenirNext-BoldItalic", "AvenirNext-Heavy", "AvenirNext-Bold", "AvenirNext-UltraLight"]
Family: Avenir Next Condensed Font names: ["AvenirNextCondensed-Heavy", "AvenirNextCondensed-MediumItalic", "AvenirNextCondensed-Regular", "AvenirNextCondensed-UltraLightItalic", "AvenirNextCondensed-Medium", "AvenirNextCondensed-HeavyItalic", "AvenirNextCondensed-DemiBoldItalic", "AvenirNextCondensed-Bold", "AvenirNextCondensed-DemiBold", "AvenirNextCondensed-BoldItalic", "AvenirNextCondensed-Italic", "AvenirNextCondensed-UltraLight"]
Family: Baskerville Font names: ["Baskerville-SemiBoldItalic", "Baskerville-SemiBold", "Baskerville-BoldItalic", "Baskerville", "Baskerville-Bold", "Baskerville-Italic"]
Family: Bodoni 72 Font names: ["BodoniSvtyTwoITCTT-Bold", "BodoniSvtyTwoITCTT-BookIta", "BodoniSvtyTwoITCTT-Book"]
Family: Bodoni 72 Oldstyle Font names: ["BodoniSvtyTwoOSITCTT-BookIt", "BodoniSvtyTwoOSITCTT-Book", "BodoniSvtyTwoOSITCTT-Bold"]
Family: Bodoni 72 Smallcaps Font names: ["BodoniSvtyTwoSCITCTT-Book"]
Family: Bodoni Ornaments Font names: ["BodoniOrnamentsITCTT"]
Family: Bradley Hand Font names: ["BradleyHandITCTT-Bold"]
Family: Chalkboard SE Font names: ["ChalkboardSE-Bold", "ChalkboardSE-Light", "ChalkboardSE-Regular"]
Family: Chalkduster Font names: ["Chalkduster"]
Family: Charter Font names: ["Charter-BlackItalic", "Charter-Bold", "Charter-Roman", "Charter-Black", "Charter-BoldItalic", "Charter-Italic"]
Family: Cochin Font names: ["Cochin-Italic", "Cochin-Bold", "Cochin", "Cochin-BoldItalic"]
Family: Copperplate Font names: ["Copperplate-Light", "Copperplate", "Copperplate-Bold"]
Family: Courier Font names: ["Courier-BoldOblique", "Courier-Oblique", "Courier", "Courier-Bold"]
Family: Courier New Font names: ["CourierNewPS-ItalicMT", "CourierNewPSMT", "CourierNewPS-BoldItalicMT", "CourierNewPS-BoldMT"]
Family: DIN Alternate Font names: ["DINAlternate-Bold"]
Family: DIN Condensed Font names: ["DINCondensed-Bold"]
Family: Damascus Font names: ["DamascusBold", "DamascusLight", "Damascus", "DamascusMedium", "DamascusSemiBold"]
Family: Devanagari Sangam MN Font names: ["DevanagariSangamMN", "DevanagariSangamMN-Bold"]
Family: Didot Font names: ["Didot-Bold", "Didot", "Didot-Italic"]
Family: Euphemia UCAS Font names: ["EuphemiaUCAS", "EuphemiaUCAS-Italic", "EuphemiaUCAS-Bold"]
Family: Farah Font names: ["Farah"]
Family: Futura Font names: ["Futura-CondensedExtraBold", "Futura-Medium", "Futura-Bold", "Futura-CondensedMedium", "Futura-MediumItalic"]
Family: Galvji Font names: ["Galvji-Bold", "Galvji"]
Family: Geeza Pro Font names: ["GeezaPro-Bold", "GeezaPro"]
Family: Georgia Font names: ["Georgia-BoldItalic", "Georgia-Italic", "Georgia", "Georgia-Bold"]
Family: Gill Sans Font names: ["GillSans-Italic", "GillSans-SemiBold", "GillSans-UltraBold", "GillSans-Light", "GillSans-Bold", "GillSans", "GillSans-SemiBoldItalic", "GillSans-BoldItalic", "GillSans-LightItalic"]
Family: Helvetica Font names: ["Helvetica-Oblique", "Helvetica-BoldOblique", "Helvetica", "Helvetica-Light", "Helvetica-Bold", "Helvetica-LightOblique"]
Family: Helvetica Neue Font names: ["HelveticaNeue-UltraLightItalic", "HelveticaNeue-Medium", "HelveticaNeue-MediumItalic", "HelveticaNeue-UltraLight", "HelveticaNeue-Italic", "HelveticaNeue-Light", "HelveticaNeue-ThinItalic", "HelveticaNeue-LightItalic", "HelveticaNeue-Bold", "HelveticaNeue-Thin", "HelveticaNeue-CondensedBlack", "HelveticaNeue", "HelveticaNeue-CondensedBold", "HelveticaNeue-BoldItalic"]
Family: Hiragino Maru Gothic ProN Font names: ["HiraMaruProN-W4"]
Family: Hiragino Mincho ProN Font names: ["HiraMinProN-W3", "HiraMinProN-W6"]
Family: Hiragino Sans Font names: ["HiraginoSans-W3", "HiraginoSans-W6", "HiraginoSans-W7"]
Family: Hoefler Text Font names: ["HoeflerText-Italic", "HoeflerText-Black", "HoeflerText-Regular", "HoeflerText-BlackItalic"]
Family: Kailasa Font names: ["Kailasa-Bold", "Kailasa"]
Family: Kefa Font names: ["Kefa-Regular"]
Family: Khmer Sangam MN Font names: ["KhmerSangamMN"]
Family: Kohinoor Bangla Font names: ["KohinoorBangla-Regular", "KohinoorBangla-Semibold", "KohinoorBangla-Light"]
Family: Kohinoor Devanagari Font names: ["KohinoorDevanagari-Regular", "KohinoorDevanagari-Light", "KohinoorDevanagari-Semibold"]
Family: Kohinoor Gujarati Font names: ["KohinoorGujarati-Light", "KohinoorGujarati-Bold", "KohinoorGujarati-Regular"]
Family: Kohinoor Telugu Font names: ["KohinoorTelugu-Regular", "KohinoorTelugu-Medium", "KohinoorTelugu-Light"]
Family: Lao Sangam MN Font names: ["LaoSangamMN"]
Family: Malayalam Sangam MN Font names: ["MalayalamSangamMN-Bold", "MalayalamSangamMN"]
Family: Marker Felt Font names: ["MarkerFelt-Thin", "MarkerFelt-Wide"]
Family: Menlo Font names: ["Menlo-BoldItalic", "Menlo-Bold", "Menlo-Italic", "Menlo-Regular"]
Family: Mishafi Font names: ["DiwanMishafi"]
Family: Mukta Mahee Font names: ["MuktaMahee-Light", "MuktaMahee-Bold", "MuktaMahee-Regular"]
Family: Myanmar Sangam MN Font names: ["MyanmarSangamMN", "MyanmarSangamMN-Bold"]
Family: Noteworthy Font names: ["Noteworthy-Bold", "Noteworthy-Light"]
Family: Noto Nastaliq Urdu Font names: ["NotoNastaliqUrdu", "NotoNastaliqUrdu-Bold"]
Family: Noto Sans Kannada Font names: ["NotoSansKannada-Bold", "NotoSansKannada-Light", "NotoSansKannada-Regular"]
Family: Noto Sans Myanmar Font names: ["NotoSansMyanmar-Regular", "NotoSansMyanmar-Bold", "NotoSansMyanmar-Light"]
Family: Noto Sans Oriya Font names: ["NotoSansOriya-Bold", "NotoSansOriya"]
Family: Optima Font names: ["Optima-ExtraBlack", "Optima-BoldItalic", "Optima-Italic", "Optima-Regular", "Optima-Bold"]
Family: Palatino Font names: ["Palatino-Italic", "Palatino-Roman", "Palatino-BoldItalic", "Palatino-Bold"]
Family: Papyrus Font names: ["Papyrus-Condensed", "Papyrus"]
Family: Party LET Font names: ["PartyLetPlain"]
Family: PingFang HK Font names: ["PingFangHK-Medium", "PingFangHK-Thin", "PingFangHK-Regular", "PingFangHK-Ultralight", "PingFangHK-Semibold", "PingFangHK-Light"]
Family: PingFang SC Font names: ["PingFangSC-Medium", "PingFangSC-Semibold", "PingFangSC-Light", "PingFangSC-Ultralight", "PingFangSC-Regular", "PingFangSC-Thin"]
Family: PingFang TC Font names: ["PingFangTC-Regular", "PingFangTC-Thin", "PingFangTC-Medium", "PingFangTC-Semibold", "PingFangTC-Light", "PingFangTC-Ultralight"]
Family: Rockwell Font names: ["Rockwell-Italic", "Rockwell-Regular", "Rockwell-Bold", "Rockwell-BoldItalic"]
Family: Savoye LET Font names: ["SavoyeLetPlain"]
Family: Sinhala Sangam MN Font names: ["SinhalaSangamMN-Bold", "SinhalaSangamMN"]
Family: Snell Roundhand Font names: ["SnellRoundhand", "SnellRoundhand-Bold", "SnellRoundhand-Black"]
Family: Symbol Font names: ["Symbol"]
Family: Tamil Sangam MN Font names: ["TamilSangamMN", "TamilSangamMN-Bold"]
Family: Thonburi Font names: ["Thonburi", "Thonburi-Light", "Thonburi-Bold"]
Family: Times New Roman Font names: ["TimesNewRomanPS-ItalicMT", "TimesNewRomanPS-BoldItalicMT", "TimesNewRomanPS-BoldMT", "TimesNewRomanPSMT"]
Family: Trebuchet MS Font names: ["TrebuchetMS-Bold", "TrebuchetMS-Italic", "Trebuchet-BoldItalic", "TrebuchetMS"]
Family: Verdana Font names: ["Verdana-Italic", "Verdana", "Verdana-Bold", "Verdana-BoldItalic"]
Family: Zapf Dingbats Font names: ["ZapfDingbatsITC"]
Family: Zapfino Font names: ["Zapfino"]

UITextViewをSwiftUIで用いる。

makeUIView, updateUIView, makeCoordinatorを実装する。Coordinatorはdelegateを処理する。

import UIKit
import Combine
// https://www.appcoda.com/swiftui-textview-uiviewrepresentable/
struct EditView: UIViewRepresentable {
    
    @Binding var text: String
    @Binding var textStyle:UIFont.TextStyle
    
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        
        textView.font = UIFont.preferredFont(forTextStyle: textStyle)
        textView.autocapitalizationType = .sentences
        textView.isSelectable = true
        textView.isUserInteractionEnabled = true
        textView.delegate = context.coordinator
        
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
        uiView.font = UIFont.preferredFont(forTextStyle: textStyle)
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator($text)
    }
}

class Coordinator: NSObject, UITextViewDelegate {
    var text: Binding<String>
    
    init(_ text:Binding<String>) {
        self.text = text
    }
    
    func textViewDidChange(_ textView: UITextView) {
        self.text.wrappedValue = textView.text
    }
    
    func textViewDidBeginEditing(_ textView: UITextView) {
    }
}

使う側は単に呼び出す

struct ContentView: View {
    
    @State private var message = "x"
    @State private var textStyle = UIFont.TextStyle.body
    
    var body: some View {
        VStack(alignment: .leading) {
            Button(action: {
                UIApplication.shared.endEditing()
            }, label: {
                Text("Push")
            })
            Text(message)
            EditView(text: $message, textStyle: $textStyle)
        }
    }
}

今回、キーボードを下げるのは

import UIKit

extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

というextensionを用いた。

簡単な画面遷移

WebからJSON取得して表示するリスト画面と設定画面の遷移をするっでもを作ってみた。 設定画面でwebから取得するかローカルファイルから取得するか選べる。

まずモデルは

public struct Article: Codable {
    let title:String
    let url:String
}

という簡単なもの。

ローカルファイルにはJSON形式で

[{"title":"[pre]【Unity】FungusをLuaで使用する方法","url":"https:\/\/qiita.com\/Humimaro\/items\/76e1730dde5c359f61ba"},...]

のようなものを準備しておく。

ファイルのロードは

import Foundation
import Combine

func load<T:Decodable>(_ filename:String) -> AnyPublisher<T, Error> {
    let data:Data
    
    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
        else {
            return Fail(error: NSError(domain: "FILE", code: 1, userInfo: nil)).eraseToAnyPublisher()
    }
    
    
    do {
        data = try Data(contentsOf: file)
    } catch  {
        return Fail(error: NSError(domain: "FILE_DATA", code: 1, userInfo: nil)).eraseToAnyPublisher()
    }
    
    do {
        let decoder = JSONDecoder()
        let obj = try decoder.decode(T.self, from: data)
        return Just(obj).setFailureType(to: Error.self).eraseToAnyPublisher()
    } catch {
        return Fail(error: error).eraseToAnyPublisher()
    }
}

のように、Combineを利用してみた。

Webから取得するところは

import Combine
import Foundation

var cancellables = [AnyCancellable]()

// 記事を取得する。
func fetchArticles(isPreview:Bool) -> AnyPublisher<[Article], Error> {
    
    if isPreview {
        return load("articles.json")
    }
    
    let url = URL(string: "https://qiita.com/api/v2/items")!
    let request = URLRequest(url:url)
    
    return URLSession.shared
        .dataTaskPublisher(for: request)
        .tryMap({ (data, response) -> Data in
            guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
                throw URLError(URLError.Code.badServerResponse)
            }
            return data
        })
        .decode(type: [Article].self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

のように書き、引数によって、ファイルから取得できるようにしてみた。

ViewModelは

import Foundation
import SwiftUI
import Combine

// 記事ビューモデル
class ViewModel : ObservableObject {
    @Published private(set) var text: String = "Hello, World!"
    @Published private(set) var articles:[Article] = []
    let previewFlg:Bool
    var cancels = [AnyCancellable]()
    
    init(isPreview:Bool = false) {
        previewFlg = isPreview
    }
    
    // タップした
    func onTapped() {
        // 記事を取得する
        fetchArticles(isPreview: previewFlg)
         .receive(on: DispatchQueue.main)
         .sink(receiveCompletion: { result in
            switch result {
            case .failure(let error):
                self.text = error.localizedDescription
            case .finished:
                break
            }
         }) { articles in
            self.articles = articles
         }.store(in: &cancels)
    }
}

のように、タップしたらフェッチしてそれをarticlesに入れるという動きを書いた。

これに対応するContentViewを

import SwiftUI
import Combine


struct ContentView: View {
    @ObservedObject var viewModel:ViewModel
    @EnvironmentObject var settingModel:SettingModel
    var body: some View {
        VStack {
            Toggle(isOn:$settingModel.testMode) {
                Text("")
            }
//            if self.settingModel.testMode {
//                Text("testMode")
//            }
            Button(action: {
                self.viewModel.onTapped()
            }) {
                Text("更新")
            }
            List(viewModel.articles, id: \.title) { article in
                HStack {
                    Text(article.title)
                    Spacer()
                    Button("open") {
                        UIApplication.shared.open(
                            URL(string: article.url)!
                        )
                    }
                }
            }
            Text(viewModel.text)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(viewModel: ViewModel(isPreview: true)).environmentObject(SettingModel())
    }
}

のようにした。リスト表示の部分が先程のarticlesの表示をする。 設定からの情報を表示するためにenvironmentObjectも使っている。

設定画面は

import SwiftUI

struct SettingView: View {
    @EnvironmentObject var settingModel:SettingModel

    var body: some View {
        VStack {
            HStack {
                Text("TestMode")
                Spacer()
                Toggle(isOn: $settingModel.testMode) {
                    Text(settingModel.testMode ? "":"")
                }
            }.padding()
        }
    }
}

struct Setting_Previews: PreviewProvider {
    static var previews: some View {
        SettingView().environmentObject(SettingModel())
    }
}

という単純なもの、settingModelはenvironmentObjectでSceneDelegateで注入されていて

import Foundation
import Combine
import SwiftUI

// 設定モデル
class SettingModel : ObservableObject {
    @Published var testMode: Bool = false
}

BaseViewはこの2つをとりまとめていて、

import SwiftUI

struct BaseView: View {
    @State var setting:Bool = false
    var body: some View {
        NavigationView {
            VStack {
                Button("設定") {
                    self.setting = true
                    print("x")
                }
                ContentView(viewModel: ViewModel())
                NavigationLink(destination: SettingView(), isActive: $setting) {
                    EmptyView()
                }
            }
        }.navigationViewStyle(StackNavigationViewStyle())
    }
}

struct BaseView_Previews: PreviewProvider {
    static var previews: some View {
        BaseView().environmentObject(SettingModel())
    }
}

のようにNavigationLinkで設定画面に移動できるようにしている。

これで実装できたが、実際は設定画面の情報をContentのViewModelに渡せていなくて ローカルファイルの読み込みができていない。 そのところは変える必要があるだろう。

簡単なViewModel

ごく簡単にViewModelを作ってみた。

まずモデルは

struct Article: Codable {
    let title:String
    let url:String
}

func fetchArticles() -> AnyPublisher<[Article], Error> {
    let url = URL(string: "https://qiita.com/api/v2/items")!
    let request = URLRequest(url:url)
    
    return URLSession.shared
        .dataTaskPublisher(for: request)
        .map({$0.data})
        .decode(type: [Article].self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

ViewModelはこのようにボタンタップをしたらtextが変わる形に書いておく。 ObservableObjectを継承する。

import Foundation
import SwiftUI
import Combine

class ViewModel : ObservableObject {
    @Published private(set) var text: String = "Hello, World!"
    var cancels = [AnyCancellable]()
    func onTapped() {
        fetchArticles()
         .receive(on: DispatchQueue.main)
         .sink(receiveCompletion: {_ in
         }) { articles in
            
             self.text = articles.description
         }.store(in: &cancels)
    }
}

ViewでViewModelをつなげる。@ObservedObjectを使う。

import SwiftUI
import Combine


struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    var body: some View {
        VStack {
        Button(action: {
            self.viewModel.onTapped()
        }) {
            Text("Push")
        }
            Text(viewModel.text)
        }
    }
}

ここではエラーについて議論していない。

combineの簡単な例

PassthroughSubject と CurrentValueSubject

を使ってみた。おおよそRxSwift的に使えると思った。

func test() -> Void {
    let pub = PassthroughSubject<Int, Error>()
    _ = pub
        .sink(receiveCompletion: { _ in
            
        }) {
            value in
            print("new val -> \(value)")
    }
    
    pub.send(10)
    pub.send(100)
    pub.send(completion: .finished)
    
}

//test()

func test2() -> Void {
    let pub = CurrentValueSubject<Int, Error>(100)
    _ = pub.sink(receiveCompletion: {_ in
    }, receiveValue: { (value) in
        print("new val -> \(value)")
    })
    
    pub.send(100)
    pub.send(1000)
    pub.send(completion: .finished)
    
}
//test2()

func test3() -> Void {
    let pub = [1,2,3,4,5,6,7,8,9,10].publisher
    _ = pub.sink(receiveValue: { value in
        print("new val -> \(value)")
    })
}
//test3()


func test4() -> Void {
    let pub = [1,2,3,4,5].publisher
    let pub2 = pub.map { (x) -> Int in
        return x*2
    }
    
    _ = pub2.sink(receiveValue: { value in
        print("new val -> \(value)")
    })
}
//test4()


/*
 new val -> (4, 5)
 new val -> (4, 6)
 new val -> (4, 7)
 new val -> (4, 8)
 */
func test5() -> Void {
    let pub = [1,2,3,4].publisher
    let pub2 = [5,6,7,8].publisher
    let pub3 = pub.combineLatest(pub2)
    _ = pub3.sink(receiveValue: { value in
        print("new val -> \(value)")
    })
}

//test5()

/*
 new val -> (1, 5)
 new val -> (2, 6)
 new val -> (3, 7)
 new val -> (4, 8)
 */
func test6() -> Void {
    let pub = [1,2,3,4].publisher
    let pub2 = [5,6,7,8].publisher
    let pub3 = pub.zip(pub2)
    _ = pub3.sink(receiveValue: { value in
        print("new val -> \(value)")
    })
}

//test6()

func test7() -> Void {
    let sub = PassthroughSubject<Int, Error>()
    _ = sub.sink(receiveCompletion: { comp in
        print("\(comp)")
    }, receiveValue: { (value) in
        print("new val -> \(value)")
    })
    
    sub.send(100)
    sub.send(120)
    sub.send(completion: .failure(NSError(domain: "Error!", code: 0, userInfo: nil)))
}

test7()

ファイル読み込みで一行毎に検索

import sys

# 該当言語
searchWord = "hoge"

# ファイル読み込み
text = open(sys.argv[1], "r")
# 一行づつ読み出し
for line in text:
    # 該当単語があるなら書き出し
    if searchWord in line:
        print(line)

# 閉じる
text.close()

Combine

細かいことは無視してひとまず動かしてみる例を勉強してみた。

JSONを読み込む用のモデル

struct Article: Codable {
    let title:String
    let url:String
}

読み込む

func fetchArticles() -> AnyPublisher<[Article], Error> {
    let url = URL(string: "https://qiita.com/api/v2/items")!
    let request = URLRequest(url:url)
    
    return URLSession.shared
        .dataTaskPublisher(for: request)
        .map({$0.data})
        .decode(type: [Article].self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

無理やり表示させてみる。

import SwiftUI
import Combine

var cancels = [AnyCancellable]()

struct ContentView: View {
    @State var tex = "x"
    var body: some View {
        VStack {
        Button(action: {
           fetchArticles()
            .sink(receiveCompletion: {_ in
            }) { articles in
                self.tex = articles.description
            }.store(in: &cancels)
        }) {
            Text("a")
        }
            Text(tex)
        }
    }
}

これでひとまず動かせた。 AnyPublisherがRxSwiftのObject的なもの sinkで購読、storeでキャンセル待ちを保存しておく。 実際の実装ではViewでfetch等はしないだろう。

UIKitとの連携

チュートリアルより。 UIPageViewControllerをSwiftUIで使う。

まず、使いたいUIViewControllerをwrapするクラスを作る。 そのクラスはUIViewControllerRepresentableを実装する。

import Foundation
import SwiftUI
import UIKit

struct PageViewController: UIViewControllerRepresentable {
    
    typealias UIViewControllerType = UIPageViewController
    
    var controllers:[UIViewController]

    // ページ情報
    @Binding var currentPage:Int
    
  // 関連するdataSorce, delegate等のクラスはCoordinaterとして渡す。
    func makeCoordinator() -> Coordinater {
        Coordinater(self)
    }
    
 // ここでViewControllerを作って渡す。delegate等もcontextを通じてつなげる。
    func makeUIViewController(context contetxt:Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .
            horizontal)
        pageViewController.dataSource = contetxt.coordinator
        pageViewController.delegate = contetxt.coordinator
        return pageViewController
    }
    
// 更新
    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        pageViewController.setViewControllers([controllers[currentPage]], direction: .forward, animated: true)
    }
    
// DataSorce, Delegate等はCoordinaterとして宣言する。
    class Coordinater: NSObject, UIPageViewControllerDataSource,UIPageViewControllerDelegate {
        var parent:PageViewController
        init(_ pageViewController:PageViewController) {
            self.parent = pageViewController
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
            guard let index = parent.controllers.firstIndex(of: viewController) else {
                return nil
            }
            if index == 0 {
                return parent.controllers.last;
            }
            return parent.controllers[index - 1]
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
            guard let index = parent.controllers.firstIndex(of: viewController) else {
                           return nil
                       }
                       if index + 1 == parent.controllers.count {
                           return parent.controllers.first
                       }
                       return parent.controllers[index + 1]
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
            if completed, let visibleViewController = pageViewController.viewControllers?.first,
                let inedx = parent.controllers.firstIndex(of:visibleViewController) {
                parent.currentPage = inedx
            }
        }
        
        
    }
}

UIViewも似た感じで扱える。UIViewRepresentableを実装する。

import SwiftUI
import UIKit

struct PageControl: UIViewRepresentable {
    var numberOfPages:Int
    @Binding var currentPage:Int
    
    func makeUIView(context:Context) -> UIPageControl {
        let control = UIPageControl()
        control.numberOfPages = numberOfPages
       // controlのtarget, actionをつないでいる。
        control.addTarget(context.coordinator, action: #selector(Coordinator.updateCurrentPage(sender:)), for:  .valueChanged)
        return control
    }
    
    // 表示ロジック用のクラスを作る。
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
  // 更新
    func updateUIView(_ uiView: UIPageControl, context: UIViewRepresentableContext<PageControl>) {
        uiView.currentPage = currentPage
    }
    
    class Coordinator: NSObject {
        var control: PageControl
        
        init(_ control:PageControl) {
            self.control = control
        }
        
        @objc func updateCurrentPage(sender:UIPageControl) {
            control.currentPage = sender.currentPage
        }
    }

}

これらのプロトコルを実装すれば普通のビューのように扱える。

import SwiftUI

struct PageView<Page:View>: View {
/// UIPageViewControllerではUIViewController達として扱うのでSwiftUIのViewをUIHostViewControllerでくるんで扱う。
    var viewControllers:[UIHostingController<Page>]
    @State var currentPage = 0
    init(_ views:[Page])  {
        self.viewControllers = views.map {
            UIHostingController(rootView: $0)
        }
    }
    
    var body: some View {
            ZStack(alignment: .bottomTrailing){
                PageViewController(controllers: viewControllers, currentPage: $currentPage)
                PageControl(numberOfPages: viewControllers.count, currentPage: $currentPage)
                    .padding(.trailing)
            }
    }
}

struct PageView_Previews: PreviewProvider {
    static var previews: some View {
        PageView(features.map { FeatureCard(landmark: $0)})
            .aspectRatio(3/2, contentMode: .fit)
    }
}