I am trying to replicate the timer picker from the Clock app, shown below, using SwiftUI.
DatePicker doesn't have a countDownTimer mode like UIDatePicker does, but then that doesn't show seconds even though the countDownDuration property is in seconds.
I am currently trying to use a custom UIPickerView with two components (minutes and seconds), all wrapped inside a UIViewRepresentable.
This sort of works, but I can't seem to get the min and sec labels to be positioned where they are.
Any help on this matter would be much appreciated.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I am attempting to create a custom font picker (similar to the one in Pages) using SwiftUI, because the current UIFontPickerViewController isn't sufficient enough for my app.
When running the app and presenting FontPickerView in a sheet, the app seems to pause, the sheet doesn't appear, and a lot of dialog continuously pops up in the Xcode console.
This is an example of what keeps showing:
CoreText note: Client requested name ".SFUI-Regular", it will get TimesNewRomanPSMT rather than the intended font. All system UI font access should be through proper APIs such as CTFontCreateUIFontForLanguage() or +[UIFont systemFontOfSize:].
The .SFUI string changes with different font styles, for example -Bold, -Compressed, -SemiExpandedLight...
I believe the problem lies when accessing the UIFont family and font name properties and methods.
Is there a reason why this is happening? A possible solution?
Help is appreciated.
Here is some code you can test (Xcode 13 beta 3):
extension Character {
var isUppercase: Bool {
String(self).uppercased() == String(self)
}
}
extension UIFont {
// Will show dialog from here
class func familyName(forFontName fontName: String) -> String {
var familyName = ""
familyNames.forEach { family in
fontNames(forFamilyName: family).forEach { font in
if font == fontName {
familyName = family
}
}
}
return familyName
}
}
struct FontPickerView: View {
@Environment(\.dismiss) private var dismiss
@ScaledMetric private var linkPaddingLength = 24
@State private var searchText = ""
@State private var linkSelection: String?
@Binding var selectedFontName: String
// Will show dialog from here
private var familyNames: [String] {
UIFont.familyNames.filter {
$0.contains(searchText) || searchText.isEmpty
}
}
// Will show dialog from here
private var familyPickerBinding: Binding<String> {
Binding {
UIFont.familyName(forFontName: selectedFontName)
} set: {
selectedFontName = UIFont.fontNames(forFamilyName: $0)[0]
}
}
var body: some View {
NavigationView {
List {
Picker("All Fonts", selection: familyPickerBinding) {
ForEach(familyNames, id: \.self, content: pickerRow)
}
.labelsHidden()
.pickerStyle(.inline)
}
.listStyle(.insetGrouped)
.searchable(text: $searchText, placement: .navigationBarDrawer)
.navigationTitle("Fonts")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
Button("Cancel", action: dismiss.callAsFunction)
}
}
}
private func linkPadding(forFamilyName familyName: String) -> CGFloat {
familyPickerBinding.wrappedValue == familyName ? 0 : linkPaddingLength
}
private func familyText(forFamilyName familyName: String) -> some View {
Text(familyName)
.font(.custom(familyName, size: UIFont.labelFontSize))
}
private func familyTextWithStyles(forFamilyName familyName: String) -> some View {
ZStack(alignment: .leading) {
NavigationLink("Style options", tag: familyName, selection: $linkSelection) {
FontStylesView(selection: $selectedFontName, family: familyName)
}
.hidden()
HStack {
familyText(forFamilyName: familyName)
Spacer()
Button(action: { linkSelection = familyName }) {
Label("Style options", systemImage: "info.circle")
.labelStyle(.iconOnly)
.imageScale(.large)
}
.buttonStyle(.borderless)
.padding(.trailing, linkPadding(forFamilyName: familyName))
}
}
}
private func pickerRow(forFamilyName familyName: String) -> some View {
Group {
if UIFont.fontNames(forFamilyName: familyName).count == 1 {
familyText(forFamilyName: familyName)
} else {
familyTextWithStyles(forFamilyName: familyName)
}
}
}
}
struct FontStylesView: View {
@Binding var selection: String
let family: String
var body: some View {
List {
Picker("Font Styles", selection: $selection) {
// Will show dialog from here
ForEach(UIFont.fontNames(forFamilyName: family), id: \.self) { font in
Text(fontType(forFontName: font))
.font(.custom(font, size: UIFont.labelFontSize))
}
}
.labelsHidden()
.pickerStyle(.inline)
}
.navigationTitle(family)
}
private func fontType(forFontName fontName: String) -> String {
if let index = fontName.lastIndex(of: "-") {
var text = String(fontName.suffix(from: index).dropFirst())
// Add spaces between words.
let indexes = text.enumerated().filter { $0.element.isUppercase }.map { $0.offset }
var count = 0
indexes.forEach { index in
guard index > 0 else { return }
text.insert(" ", at: text.index(text.startIndex, offsetBy: index + count))
count += 1
}
return text
} else {
return "Regular"
}
}
}
(There are a few bugs that I am going to fix.)