Итак, мы возились с SwiftUI, и все работает, за исключением того, что список сбрасывает свою позицию прокрутки в верхнюю часть всякий раз, когда выбирается элемент.Ничто, что я делаю, не исправляет это.Код -
struct FontSettingsView : View {
@ObjectBinding var settings: FontSettings
var body: some View {
NavigationView {
ContentView(settings: settings)
.navigationBarTitle(Text("Font"), displayMode: .inline)
}
}
struct ContentView: View {
@State var settings: FontSettings
var body: some View {
VStack {
HeaderView(settings: $settings)
FontListView(excludedFontNames: settings.excludedFontNames) { fontName in
self.$settings.name.value = fontName
}
}
}
struct HeaderView: View {
@Binding var settings: FontSettings
var body: some View {
VStack {
HStack {
VStack (alignment: .leading) {
Text("Custom font size:")
.font(.body)
Text("If disabled, will use system settings")
.font(.caption)
}
Toggle(isOn: $settings.isCustomSize) {
Text("")
}
}
Slider(value: $settings.size, from: 10, through: 30, by: 0.1)
.disabled($settings.isCustomSize.value == false)
Text("Current font: \($settings.name.value)")
.customFont(using: self.settings)
.padding()
.animation(.default)
}.padding()
}
}
struct FontListView: View {
struct FontName: Identifiable {
var id = UUID()
let value: String
}
private let fontNames: [FontName]
private let tapped: ((String) -> ())?
init(excludedFontNames: [String] = [], tapped: ((String) -> ())?) {
self.fontNames = UIFont.familyNames.sorted().filter({ excludedFontNames.contains($0) == false }).map { FontName(value: $0) }
self.tapped = tapped
}
var body: some View {
List(fontNames) { fontName in
Button(action: {
self.tapped?(fontName.value)
}) {
Text(fontName.value).customFont(named: fontName.value)
}
}
}
}
}
}
К сожалению, вся документация и примеры связаны с переходом на новый экран при выборе элемента, что не является моим намерением.
Некоторый дополнительный код длялюбой, кто хочет запустить все это -
class FontSettings: BindableObject {
private static let defaultSize: CGFloat = 20
var didChange = PassthroughSubject<Void, Never>()
var excludedFontNames: [String] = []
var name: String = "Helvetica" {
didSet {
self.didChange.send()
}
}
var size: CGFloat = FontSettings.defaultSize {
didSet {
self.didChange.send()
}
}
var isCustomSize: Bool = false {
didSet {
if self.isCustomSize == false {
self.size = FontSettings.defaultSize
}
self.didChange.send()
}
}
}
extension View {
func customFont(named fontName: String, style: UIFont.TextStyle = .body) -> Self.Modified<CustomFont> {
return self.modifier(CustomFont(fontName: fontName, textStyle: style))
}
func customFont(named fontName: String, size: CGFloat) -> Self.Modified<CustomFont> {
return self.modifier(CustomFont(fontName: fontName, size: size))
}
func customFont(using settings: FontSettings) -> Self.Modified<CustomFont> {
if settings.isCustomSize {
return self.modifier(CustomFont(fontName: settings.name, size: settings.size))
}
return self.modifier(CustomFont(fontName: settings.name, textStyle: .body))
}
}
struct CustomFont: ViewModifier {
let fontName: String
let fontSize: CGFloat?
let textStyle: UIFont.TextStyle?
init(fontName: String, textStyle: UIFont.TextStyle) {
self.fontName = fontName
self.textStyle = textStyle
self.fontSize = nil
}
init(fontName: String, size: CGFloat) {
self.fontName = fontName
self.fontSize = size
self.textStyle = nil
}
// trigger view refresh when the ContentSizeCategory changes
@Environment(\.sizeCategory) var sizeCategory: ContentSizeCategory
func body(content: Content) -> some View {
if let fontSize = self.fontSize {
return content.font(.custom(self.fontName, size: fontSize))
}
guard let textStyle = self.textStyle, let size = self.fontSizes[textStyle] else {
fatalError("Unrecognized textStyle")
}
let fontMetrics = UIFontMetrics(forTextStyle: textStyle)
let fontSize = fontMetrics.scaledValue(for: size)
return content.font(.custom(self.fontName, size: fontSize))
}
// normal font sizes per style, as defined by Apple
private let fontSizes: [UIFont.TextStyle: CGFloat] = [
.largeTitle: 34,
.title1: 28,
.title2: 22,
.title3: 20,
.headline: 17,
.body: 17,
.callout: 16,
.subheadline: 15,
.footnote: 13,
.caption1: 12,
.caption2: 11
]
}