Categories
Tags
Alamofire Android AppStoreConnect AWS Bun C++ cAdvisor CFW Cloudflare Cloudflare Access Cloudflare KV Cloudflare Tunnel Cloudflare Workers D1 Deno DevContainer Discord Docker ECR ECS Edizon Emulator EmuMMC Fastlane Firestore Frida Ghidra Git GitHub GitHub Actions GitLab GPG Grafana HACGUI Heroku Homebrew Hono IDA Pro iOS IPSwitch Jailbreak Javascript JSON JWT LanPlay Linode macOS Mirakurun MongoDB NestJS NextJS Nintendo Nintendo Switch NodeJS PHP PostgreSQL Prisma Programming Prometheus Python React Realm RealmSwift Ruby Salmon Run Salmonia3+ Shogi Sideload Snap Splatoon Splatoon2 Splatoon3 SSH Stable Diffusion Starlight Swift Swift Package SwiftUI Switch TensorRT Turf War Typescript TypeScript Ubuntu Ubuntu Server VNC VPN VSCode Vue WARP Wireguard XCode Xcode yarn zsh 家電 横歩取り
610 words
3 minutes
SwiftUI+Introspect
Introspect
Introspect は SwiftUI のコンポーネントの裏に UIKit がいることを利用して SwiftUI のコードでは直接カスタマイズできないコンポーネントのかゆいところに手を届かせるためのライブラリです。
ScrollView
SwiftUI でScrollView
にはrefreshable
が効かないのですが、Introspect を使えば効くようにできます。
UIViewRepresentable
を利用する方法などもあるらしいのですが、こちらのほうが圧倒的に楽です。
import Introspect
import SwiftUI
extension ScrollView {
/// Marks ScrollView as refreshable.
func refreshable(action: @escaping @Sendable () async -> Void) -> some View {
self
.introspectScrollView(customize: { uiScrollView in
let refreshControl: UIRefreshControl = UIRefreshControl()
let action: UIAction = UIAction(handler: { handler in
let sender = handler.sender as? UIRefreshControl
sender?.endRefreshing()
Task {
await action()
}
})
refreshControl.addAction(action, for: .valueChanged)
uiScrollView.refreshControl = refreshControl
})
}
}
なんか@Sendable
を書いているとちょっとおかしいこともあるっぽいので要らないなら消していいかもしれない。
UINavigationController
NavigationView
で戻るを好きなボタンに変えたいときに使うライフハック。
import Introspect
import SwiftUI
struct CustomBackButton: ViewModifier {
@Environment(\.colorScheme) var colorScheme
@Environment(\.dismiss) var dismiss
func body(content: Content) -> some View {
content
.introspectNavigationController(customize: { nvc in
nvc.navigationBar.backIndicatorImage = UIImage()
nvc.navigationBar.backIndicatorTransitionMaskImage = UIImage()
nvc.navigationBar.topItem?.backBarButtonItem = UIBarButtonItem(
title: nil, image: UIImage(named: "ButtonType/BackArrow"), primaryAction: nil, menu: nil)
nvc.navigationBar.tintColor = colorScheme == .dark ? .white : .black
})
}
}
extension View {
func navigationBarBackButtonHidden() -> some View {
self.modifier(CustomBackButton())
}
}
で、これ思った人いると思うんですよ、以下のコードで同じことができるんじゃないかって。
import SwiftUI
struct CustomBackButton: ViewModifier {
@Environment(\.colorScheme) var colorScheme
@Environment(\.dismiss) var dismiss
func body(content: Content) -> some View {
content
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(
action: {
dismiss()
},
label: {
Image(name: "ButtonType/BackArrow")
}
).tint(colorScheme == .dark ? .black : .white)
}
}
}
}
extension View {
func navigationBarBackButtonHidden() -> some View {
self.modifier(CustomBackButton())
}
}
で、だいたいこれでも思った通りの挙動をするのですが、標準のスワイプバックが効かなくなるという問題あります。これが困るんですよね。
ちなみに上記の方法でも困る点があって、指定されたNavigationView
よりも下位の階層にあるコンポーネントにも強制的に反映されてしまうという問題があります。ただ、今回はスワイプバックが効かなくなる方が困るので、こちらの案を採用しました。
NavigationHeaderItem
import Introspect
import SwiftUI
struct CustomBackButton: ViewModifier {
@Environment(\.colorScheme) var colorScheme
@Environment(\.dismiss) var dismiss
func body(content: Content) -> some View {
content
.introspectNavigationController(customize: { nvc in
nvc.navigationBar.titleTextAttributes = [NSAttributedString.Key.font: UIFont(name: "FONT NAME", size: 16)!]
})
}
}
また何か便利そうなライフハック見つけたら掲載します。
SwiftUI+Introspect
https://fuwari.vercel.app/posts/2022/10/introspect/