しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

AndroidのEditTextやTextViewのフォントを明朝体にする

android:fontFamilyをserifにするだけです。

<TextView
    android:id="@+id/textview_first"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hello_first_fragment"
    app:layout_constraintBottom_toTopOf="@id/button_first"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:fontFamily="serif" />

f:id:llcc:20210502171809p:plain

日本語も対応しています。

f:id:llcc:20210502171836p:plain

android:typefaceをserifにしても同じ結果になります。
typefaceはboldやitalicを指定できるプロパティーです。

<TextView
    android:id="@+id/textview_first"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="こんにちは"
    app:layout_constraintBottom_toTopOf="@id/button_first"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:typeface="serif" />

【Kotlin】Applicationを継承したクラスを試してみる

AndroidのApplicationクラスを使ってみました。
Applicationを継承したクラスは全Activityからアクセスできるクラスになります。

クラスは下のように定義します。

class MainApplication: Application() {
}

下のようにAndroidManifest.xmlのapplicationタグに追加することでActivityから呼び出せるようになります。

<application android:name=".MainApplication">
</application>

Activityからは下のように呼び出す事ができます。
もしAndroidManifest.xmlへの記載がない場合、applicationにはandroid.app.Applicationクラスのインスタンスが入ります。

val app = (application as MainApplication)

ライフサイクルメソッドとしては、インスタンス作成時に呼ばれるonCreateなどがあります。

class MainApplication: Application() {
    override fun onCreate() {
        super.onCreate()
    }
}

【Kotlin】NavControllerを試してみる

Empty Activityを選んでプロジェクト作成をします。

f:id:llcc:20210323205236p:plain

起動すると下のようにラベルだけの画面が表示されます。

f:id:llcc:20210323205601p:plain

次はナビゲーションを作成します。 まず新規作成でAndroid Resource Fileを選択します。

f:id:llcc:20210323205925p:plain

Resource TypeをNavigationにしてOKを押します。

f:id:llcc:20210323205857p:plain

my_navigation.xmlが作られたので、次はここに登録するFragmentを2つ作ります。
今回はFirstFragmentとSecondFragmentという2つのフラグメントを作成しました。

f:id:llcc:20210323210344p:plain

Fragmentができたのでmy_navigation.xmlに登録します。
my_navigation.xmlを開いて画面上部の+ボタンからフラグメントを2つ追加します。

f:id:llcc:20210323210437p:plain

下のようにフラグメントが2つ登録されました。

f:id:llcc:20210323210614p:plain

次はナビゲーションを表示します。
activity_main.xmlを下のように変更します。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/my_navigation"
        app:defaultNavHost="true" />

</androidx.constraintlayout.widget.ConstraintLayout>

アプリを起動するとFirstFragmentの内容が表示されました。

f:id:llcc:20210324111557p:plain

次はSecondFragmentへの遷移を実装します。
my_navigation.xmlの右側のActionsの+からActionを作成します。

f:id:llcc:20210324111815p:plain

次はFirstFragmentに画面遷移用のボタンを作成します。
fragment_first.xmlを下のように変更します。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FirstFragment">

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fragment_first_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />
</FrameLayout>

FirstFragment.ktのonViewCreatedにボタン押下時の画面遷移処理を追加します。

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    view.findViewById<FloatingActionButton>(R.id.fragment_first_button)?.setOnClickListener {
        findNavController().navigate(R.id.action_firstFragment_to_secondFragment)
    }
}

起動するとボタンだけの画面が表示されます。
このボタンをタップすれば次の画面に移動します。

f:id:llcc:20210324112600p:plain

先程作ったActionを選択してAnimationsの所を編集すれば遷移時のアニメーションを指定できます。

f:id:llcc:20210324122213p:plain

ActionBarの戻るボタンですが、ActivityのsetupActionBarWithNavControllerを呼び出す事で表示できます。
押されたときの動作はonSupportNavigateUpで実装します。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setupActionBarWithNavController(findNavController(R.id.nav_host_fragment))
    }

    override fun onSupportNavigateUp(): Boolean {
        return findNavController(R.id.nav_host_fragment).navigateUp()
    }
}

f:id:llcc:20210324123037p:plain

次は遷移のときに変数を渡してみます。
まずはそれぞれのbuild.gradleで下のようにプラグインを設定します。

classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.4"
apply plugin: "androidx.navigation.safeargs.kotlin"

プラグインを入れたらmy_navigation.xmlを開いて遷移先を選択、画面右側Argumentsの+から変数を追加します。

f:id:llcc:20210324123624p:plain

この状態でビルドするとFirstFragmentDirectionsというクラスが生成されます。
画面遷移時のnavigateメソッドの引数にこのクラスを渡すようにします。

findNavController().navigate(FirstFragmentDirections.actionFirstFragmentToSecondFragment("testValue"))

遷移先では自動生成されたSecondFragmentArgsというクラスを使って変数を取り出します。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    arguments?.let {
        val args = SecondFragmentArgs.fromBundle(it)
        Log.d("test", "${args.testValue}")
    }
}

ログを見ると無事に渡せていることが分かります。

f:id:llcc:20210324125220p:plain

もし上記クラスが自動生成されない場合、Android studioの「Sync Project with Gradle Files」を選択すると良いかもしれません。

f:id:llcc:20210324124848p:plain

SF Symbols + UIImageでアイコンを表示する

SF SymbolsというAppleの提供しているアイコンを使える機能を試してみました。

developer.apple.com

SF Symbols + UIImageでアイコンを表示する

使い方は簡単で、下のようにUIImageの初期化でアイコン名を渡すだけです。
引数のラベルがUIImage(name: "")ではなくUIImage(systemName: "")なのでそこだけ注意して下さい。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let imageView = UIImageView(image: UIImage(systemName: "doc.badge.gearshape.fill"))
        imageView.frame.size = CGSize(width: 100, height: 100)
        imageView.center = view.center
        view.addSubview(imageView)
    }
}

実行すると下のようにアイコンを表示できます。

f:id:llcc:20210107145814p:plain

アイコン名を確認したい時は下URLからアプリをダウンロードして下さい。

developer.apple.com

アプリを起動すると下のようにアイコン一覧とアイコン名が表示されます。

f:id:llcc:20210107150047p:plain

画像の色を変えたい場合、下のようにtintColorを変更します。

imageView.tintColor = .red

画像が赤になりました。

f:id:llcc:20210107150609p:plain

画像はベクターデータの為、大きくしても劣化しません。
下はサイズを500x500に変更したものです。

f:id:llcc:20210107150540p:plain

SF Symbols + UILabelで文字の中にアイコンを入れる

SF Symbolsは画像でしか取れないので、文字の中に入れたい場合は下のようにNSTextAttachmentを使います。

let label = UILabel()
let text = NSMutableAttributedString(string: "アイコン前")
text.append(NSAttributedString(attachment: NSTextAttachment(image: UIImage(systemName: "doc.badge.gearshape.fill")!)))
text.append(NSAttributedString(string: " 後"))
label.attributedText = text
label.sizeToFit()
label.center = view.center
view.addSubview(label)

実行すると文字の間にアイコンが入った事が分かります。

f:id:llcc:20210107151055p:plain

参考URL

Apple Developer Documentation

iOS13のカスタムフォントを試してみる

iOS13から使えるようになっていたカスタムフォントのAPIを試してみました。
この機能を使うと端末へのフォントインストールやフォントの確認ができます。

まずはフォント一覧の取得をしてみようと思います。 最初にテスト用のプロジェクトを作ります。

f:id:llcc:20201031141030p:plain

プロジェクトを作ったらプロジェクト設定の「Signing & Capabilities」でFontsを有効化します。

f:id:llcc:20201031141015p:plain

Storyboardで下のようにラベルとボタンだけ配置します。

f:id:llcc:20201031142117p:plain

最後にボタンを押したらUIFontPickerViewController(フォント一覧画面)を表示するような実装をします。

class ViewController: UIViewController {
    @IBAction func tapBtn() {
        let v = UIFontPickerViewController()
        present(v, animated: true, completion: nil)
    }
}

アプリを起動してボタンを押すとフォント一覧を見る事ができました。

f:id:llcc:20201031143020p:plain

次に一覧からフォントを選んで使います。
コードは下のとおりです。
ViewControllerをUIFontPickerViewControllerDelegateに準拠させました。

class ViewController: UIViewController {
    @IBOutlet weak var testLabel: UILabel!
    
    @IBAction func tapBtn() {
        let v = UIFontPickerViewController()
        v.delegate = self
        present(v, animated: true, completion: nil)
    }
}

extension ViewController: UIFontPickerViewControllerDelegate {
    func fontPickerViewControllerDidPickFont(_ viewController: UIFontPickerViewController) {
        guard let fontDescriptor = viewController.selectedFontDescriptor else {
            return
        }
        testLabel.font = UIFont(descriptor: fontDescriptor, size: 17)
    }
}

これでフォント選択したらラベルのフォントが変わるようになりました。

f:id:llcc:20201031143619p:plain

フォント一覧ですが、filteredLanguagesPredicateを使うと日本語対応フォントのみといった絞り込みができます。

let v = UIFontPickerViewController()
v.configuration.filteredLanguagesPredicate = UIFontPickerViewController.Configuration.filterPredicate(forFilteredLanguages: ["js"])

次はフォントのインストールをしてみます。
フォントはNikkyou Sansというフォントを使わせて頂きました。

Nikkyou Sans Font | daredemotypo | FontSpace

ダウンロードしたフォントをxcassetsに追加します。

f:id:llcc:20201031151439p:plain

フォントのインストール処理は下のとおりです。
CTFontManagerRegisterFontsWithAssetNamesメソッドでxcassetsのフォントを登録できます。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        CTFontManagerRegisterFontsWithAssetNames(["NikkyouSans-B6aV"] as CFArray, nil, .user, true, { arr, result in
            print(arr)
            print(result)
            return true
        })
    }
}

アプリを起動すると下のようにインストール確認画面が表示されます。

f:id:llcc:20201031151602p:plain

インストールしたフォントは下のようにフォント一覧画面で選べるようになります。

f:id:llcc:20201031151713p:plain

【SwiftUI】GeometryReaderでViewのサイズを取得する

画面サイズによって処理を変えたかったため、GeometryReaderというクラスを使って画面サイズを取得しました。

Viewのサイズは下の形で取得できます。

struct ContentView: View {
    var body: some View {
        GeometryReader { geometry in
            Text(String("\(geometry.size)"))
        }
    }
}

実行すると下のようにViewのサイズが取得できます。

f:id:llcc:20201028105427p:plain

ただ上記ViewサイズはSafeAreaが除外されているので注意が必要です。
SafeAreaを取得したい場合はしたのように記述します。

struct ContentView: View {
    var body: some View {
        GeometryReader { geometry in
            Text(String("\(geometry.safeAreaInsets)"))
        }
    }
}

View全体ではなく、内側にあるViewのサイズを取りたい場合は下のように入れ子にします。

struct ContentView: View {
    var body: some View {
        VStack {
            Spacer().frame(height: 100)
            GeometryReader { geometry in
                Text(String("\(geometry.size)"))
            }
            Spacer().frame(height: 100)
        }
    }
}

実行すると内側のViewのサイズを取れました。

f:id:llcc:20201028110442p:plain

GeometryReaderはWidget上でも使う事ができます。

f:id:llcc:20201028110852p:plain

frameメソッドを使う事で、xとyも取得できます。
引数はCoordinateSpaceというクラスです。

struct ContentView: View {
    var body: some View {
        GeometryReader { geometry in
            Text(String("\(geometry.frame(in: .global))"))
        }
    }
}

実行結果は下のとおりです。
SafeAreaも含めた結果になるので注意が必要です。

f:id:llcc:20201028111312p:plain

CoordinateSpaceは自分でcoordinateSpaceメソッドを使って定義する事も可能です。
下の例ではVStackでTestという名前のCoordinateSpaceを定義して、それに対する位置を取得しています。

struct ContentView: View {
    var body: some View {
        VStack {
            Spacer().frame(height: 100)
            GeometryReader { geometry in
                Text(String("\(geometry.frame(in: .named("Test")))"))
            }
            Spacer().frame(height: 100)
        }.coordinateSpace(name: "Test")
    }
}

実行するとVStackに対する位置情報が取得できている事が分かります。

f:id:llcc:20201028111637p:plain

Xcode 12でbuilding for iOS Simulator, but linking in object file built for iOS, for architecture arm64というエラーが出た時の対応

Xcode 12でビルドした時、下のようなエラーが出た時の対応です。
エラーはCocoaPodsで入れたRealmで発生していました。

building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

結論としては、PodfileにEXCLUDED_ARCHSに関する記述を追加してpod installしたら動くようになりました。

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      if target.name.include?('Realm')
        config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
      end
    end
  end
end

参考にしたのは下URLです。

Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64 - Stack Overflow

詳細なエラー内容は下のとおりです。
おそらくRealmのlibrealmcore-ios.aをシミュレータ向けにビルドしようとしたけどarm64になっていたという内容です。
シミュレータはx86_64というアーキテクチャを使うので、正しく指定できてなくてエラーになったのかと思います。

MyApp/Pods/Realm/core/librealmcore-ios.a(bptree.o), building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

各端末で使っているアーキテクチャは以下URLが参考になりました。

iOS・tvOS・watchOS デバイスのアーキテクチャについてのまとめ – ymyzk’s blog

iOSデバイス一覧表 - Qiita

試しにfileコマンドでlibrealmcore-ios.aのアーキテクチャを見た所、arm64だけでなくx86_64にも対応していました。

f:id:llcc:20201006112100p:plain

対応自体はしているので、XcodeのRealmとRealmSwiftのExcluded Architecturesにarm64を指定すればx86_64でビルドできるようになります。

f:id:llcc:20201006113959p:plain

先程も書いた下処理をPodfileに書けば自動でRealmとRealmSwiftにExcluded Architecturesを設定してくれます。

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      if target.name.include?('Realm')
        config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
      end
    end
  end
end

この処理によってシミュレータの時だけarm64を除外する設定が追加されます。

f:id:llcc:20201006114732p:plain

DebugのBuild Active Architecture OnlyをYesに変える事で動かすこともできました。

f:id:llcc:20201006114402p:plain

Debugで常にBuild Active Architecture OnlyをYesにしたい場合は下処理をPodfileに記述することで実現できます。

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      if config.name == 'Debug'
        config.build_settings['ONLY_ACTIVE_ARCH'] = 'YES'
      end
    end
  end
end