Style first character in NavigationBar title in SwiftUI - swiftui

I'm trying to change the first characters font color in SwiftUI like this
But I'm getting the warning saying Only unstyled text can be used with navigationTitle(_:) when I run the app, and the text shows up as black. Since I don't want to change the whole font - changing the font color of the navigationBar is not an option. Is the only way to go to make a custom NavigationBar?
The app has iOS 14 as minimum requirements.
This is my current try for a solution... but then the whole title turns black, since styled text isn't allowed.
let titleToSet = (Text("M").foregroundColor(Color.red) + Text("y page")
NavigationView {
contentView()
.navigationTitle(titleToSet)
}

You can place any kind of view you want in the .toolbar. It doesn't have to be a button. You can roll your own title view. So, that just leaves the view itself. If you want to simply make the first letter red, then the simplest way of creating this view is by concatenating your text, after you remove the first letter from the remaining title text.
struct ToolBarStyledText: View {
var body: some View {
Text("Hello, World!")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
TitleView("My page")
}
ToolbarItem(placement: .navigationBarTrailing) {
Image(systemName: "person.circle")
}
}
}
}
struct TitleView: View {
let firstLetter: String
let remainingText: String
init(_ title: String) {
var text = title
if !text.isEmpty {
firstLetter = String(text.removeFirst())
remainingText = text
} else {
firstLetter = ""
remainingText = ""
}
}
var body: some View {
Text(firstLetter).foregroundColor(.red).bold() + Text(remainingText).bold()
}
}

Related

SwiftUI - How to make navigation bar title editable (without changing any other behavior)?

I really like the look of the navigation bar title in SwiftUI, and I like that it appears just below the safe area, but appears in the principal part of the toolbar when you scroll down. I'm wondering how to completely replicate this look and behavior but make it editable by the user (most likely through a textfield?)
I've tried
.toolbar {
ToolbarItem(placement: .principal) {
TextField("Navigation Title", text: $mainTitle)
}
}
But this simply places the title in the toolbar at all times, rather than only when you scroll slightly.
Any ideas?
First I explain why your code does not work:
Only the size of the navigationTitle changes when you start to scroll, not the size of the whole toolbar or its items.
But I think I have a solution:
import SwiftUI
struct ContentView: View {
#State private var title: String = "Title"
#State private var titleSmall: Bool = false
var body: some View {
NavigationView {
List {
GeometryReader { geo in
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
.onChange(of: geo.frame(in: .global).minY) { val in
if val <= 53.5 {
titleSmall = true
} else {
titleSmall = false
}
}
}
Text("Hello, world!")
}
.toolbar {
ToolbarItem(placement: .principal) {
TextField("Title", text: $title)
.multilineTextAlignment(.center)
.font(titleSmall ? .headline : .largeTitle.bold())
.accessibilityAddTraits(.isHeader)
}
}
}
}
}
What the code does is: It gets the top Y position from the first (in this example) list item.
Then it checks if the first list item is under the title bar and changes the font size of the title if necessary.
The only Problem I see is that there is a pretty rough transition between small and big title but I think you can figure out how to fix this.
If you have more questions how the code works just ask
I hope that solves your question.
And I would recommend to have a look at Paul Hudson’s video about the Geometry Reader (he’s a great YouTuber): https://youtu.be/kh9lnIYgW1E
I just realized that it says „OLD“ in the video title, so it may be outdated.
But he has some other videos about the Geometry Reader.
just search for „Paul Hudson Geometry Reader“

How to get a horizontal ScrollView in SwiftUI to scroll automatically to end when the enclosed Text is updated

I am writing a SwiftUI iOS app where I need a Text view to automatically scroll to the end of its content whenever the content is updated. The update happens from the model. To not complicate this question with the details of my app, I have created a simple scenario where I have two text fields and a text label. Any text entered in the text fields is concatenated and shown in the text label. The text label is enclosed in a horizontal ScrollView and can be scrolled manually if the text is longer than the screen width. What I want to achieve is for the text to scroll to the end automatically whenever the label is updated.
Here is the simple model code:
class Model: ObservableObject {
var firstString = "" {
didSet { combinedString = "\(firstString). \(secondString)." }
}
var secondString = "" {
didSet { combinedString = "\(firstString). \(secondString)." }
}
#Published var combinedString = ""
}
This is the ContentView:
struct ContentView: View {
#ObservedObject var model: Model
var body: some View {
VStack(alignment: .leading, spacing: 10) {
TextField("First string: ", text: $model.firstString)
TextField("Second string: ", text: $model.secondString)
Spacer().frame(height: 20)
Text("Combined string:")
ScrollView(.horizontal) {
Text(model.combinedString)
}
}
}
}
From the research I have done, the only way I have found to scroll to the end of the text, without having to do it manually, is to add a button to the view, which causes the text in the label to scroll to the end.
Here is the above ScrollView embedded in a ScrollViewReader, with a button to effect the scrolling action.
ScrollViewReader { scrollView in
VStack {
ScrollView(.horizontal) {
Text(model.combinedString)
.id("combinedText")
}
Button("Scroll to end") {
withAnimation {
scrollView.scrollTo("combinedText", anchor: .trailing)
}
}
.padding()
.foregroundColor(.white)
.background(Color.black)
}
}
This works, provided the intention is to use a button to effect the scrolling action.
My question is: Can the scrolling action above be triggered whenever the model is updated, without the need to click a button.
Any help or pointers will be much appreciated.
Thanks.
I assume you wanted this:
ScrollViewReader { scrollView in
VStack {
ScrollView(.horizontal) {
Text(model.combinedString)
.id("combinedText")
}
.onChange(of: model.combinedString) { // << here !!
withAnimation {
scrollView.scrollTo("combinedText", anchor: .trailing)
}
}
}
}
ScrollViewReader is the solution you're looking for. You may need to play around with the value. Also you'll need to add the .id(0) modifier to your textview.
ScrollView {
ScrollViewReader { reader in
Button("Go to first then anchor trailing.") {
value.scrollTo(0, anchor: .trailing)
}
// The rest of your code .......

String Interpolation with Text View and Textfield in Swiftui

Hi I'm wondering if there's any way to have string interpolation with a textfield and Text in Swiftui. Like
Text("hi \(TextField("Enter your name", $name)")
You can try below code
struct ContentView: View {
#State private var name = ""
var body: some View {
VStack{
TextField("Enter your name", text: $name)
Text("Hi \(name)")
}
}
}
Hope this is what you want
If you really want to do that you can put your statements in an Hstack like so:
import SwiftUI
struct SwiftUIView: View {
#State var name = ""
var body: some View {
HStack {
Text("Hi")
TextField("Name", text: $name)
.frame(width: (name.isEmpty ? 45 : 0) + CGFloat(name.count) * 9)
Text("blabla")
}
}
}
Note: This gives you a dynamic change, but not a perfect one Because the size of each character is different you will get a bigger whitespace at the end. I just choose 9 here, for char width. I still think having a extra Textfield and using the variable then later, is the better option.

Scroll up to see TextField when the keyboard appears in SwiftUI

In my use case, I have to put a TextField below the available items in a List and by using that TextField, we can add items to the List.
Initially, there're no list items (items array is empty)
Here's a minimal, reproducible example
import SwiftUI
struct ContentView: View {
#State var itemName = ""
#State var items = [String]()
var body: some View {
NavigationView {
List {
ForEach(self.items, id: \.self) {
Text($0)
}
VStack {
TextField("Item Name", text: $itemName)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
self.items.append(self.itemName)
self.itemName = ""
}) {
Text("Add Item")
}
}
}
.navigationBarTitle(Text("Title"))
}
}
}
We can add a new item to the list by typing something in the TextField and clicking "Add Item" Button , Every item that we add using TextField appears above the TextField in the List. So the TextField goes down in the List (Just like Apple’s Reminders app).
If the app has many items (more than 7 items), the keyboard covers the TextField when the keyboard appears and we can’t see the TextField.
Check this screenshot:
What I want to know is how to automatically scroll the List (move the view up) to see the TextField when keyboard appears (like in Apple's Reminders app).
I had a similar problem in my recent project, the easiest way for me to solve it was to wrap UITextField in SwiftUI and from my custom wrapper reach to the parent scroll view and tell it to scroll when the keyboard appears. I tried my approach on your project and it seems to work.
If you take my code for the wrapper and other files from this GitHub folder: https://github.com/LostMoa/SwiftUI-Code-Examples/tree/master/ScrollTextFieldIntoVisibleRange and then replace the SwiftUI TextField with my custom view (TextFieldWithKeyboardObserver) then it should scroll.
import SwiftUI
struct ContentView: View {
#State var itemName = ""
#State var items = [String]()
var body: some View {
NavigationView {
List {
ForEach(self.items, id: \.self) {
Text($0)
}
VStack {
TextFieldWithKeyboardObserver(text: $itemName, placeholder: "Item Name")
Button(action: {
self.items.append(self.itemName)
self.itemName = ""
}) {
Text("Add Item")
}
}
}
.navigationBarTitle(Text("Title"))
}
}
}
I recently wrote an article explaining this solution: https://lostmoa.com/blog/ScrollTextFieldIntoVisibleRange/

ScrollView breaks .sheet representation

I am trying to get a sheet representation work nested in multiple views inside a ScrollView.
Without the ScrollView the .sheet modifier works fine, but when I wrap everything inside the scroll view the modifier, triggers only once. So on the first tap the sheet appears fine, but after dismissing it I can not trigger it again. I am unsure whether this is a bug in SwiftUI itself or if I am doing something here.
Note: If I add the .sheet modifier to the ScrollView itself, everything is working. But for my use case the .sheet modifier is added deeply nested inside a custom view inside the ScrollView.
I am using the Xcode Beta 5
Without ScrollView - Works
struct SheetWorks: View {
#State var showSheet = false
var strings = [
"Hello", "World", "!"
]
var body: some View {
HStack {
ForEach(strings) { string in
Button(action: {self.showSheet.toggle()}) {
Text(string)
}
.sheet(isPresented: self.$showSheet) {
Text("Here is the sheet")
}
}
}
.padding()
}
}
With ScrollView - Does not work
struct SheetDoesntWork: View {
#State var showSheet = false
var strings = [
"Hello", "World", "!"
]
var body: some View {
ScrollView(.horizontal) {
HStack {
ForEach(strings) { string in
Button(action: {self.showSheet.toggle()}) {
Text(string)
}
.sheet(isPresented: self.$showSheet) {
Text("Here is the sheet")
}
}
}
.padding()
}
}
}
Maybe someone has experienced something similar or can point me in the right direction. I really appreciate any help.
Edit: This problem still persists in Beta 6
Take a look at my answer here: https://stackoverflow.com/a/57259687/554203
Basically you should use just one .sheet outside the loop and dynamically open the desired view based on a local var.
var covers = coverData
var selectedTag = 0
Group {
ForEach(covers) { item in
Button(action: {
self.selectedTag = item.tag
self.isPresented.toggle()
}) {
CoverAttributes(
title: item.title,
alternativeTitle: alternativeTitle,
tapForMore: item.tapForMore,
color: item.color,
shadowColor: item.shadowColor)
}
}
}
.sheet(isPresented: self.$isPresented, content: {
Text("Destination View \(self.selectedTag)")
// Here you could use a switch statement on selectedTag if you want
})
As of Xcode 11.1 GM Seed I can approve that the .sheet modifier now works correctly inside the ScrollView. Furthermore also a multiline Text is now correctly rendered.