SwiftUI : How I can use custom font for NavigationBarTitle Strings? - swiftui

import SwiftUI
struct AddChildrenView: View {
#State var word : String = ""
#State var meaning : String = ""
#State var isShowAlert : Bool = false
#State var isClicked : Bool = false
#State var searchText : String = ""
#EnvironmentObject var itemModel : ItemModel
var item : Item
var searchItem : [Children] {
if searchText.isEmpty {
return item.children
} else {
return item.children.filter({$0.word!.contains(searchText)})
}
}
init() {
UINavigationBar.appearance().titleTextAttributes = [.font : UIFont(name: "NotoSans-Bold", size: 20)!]
}
var body: some View {
NavigationView {
ZStack {
if item.children.count == 0 {
NochildrenView()
} else {
List {
ForEach(searchItem) {child in
WordListRowView(children: child)
.swipeActions(edge: .trailing, allowsFullSwipe: true, content: {
Button(role: .destructive, action: {
itemModel.deleteChildren(children: child, item: item)
}, label: {
Image(systemName: "trash.fill")
})
Button(action: {
itemModel.favoriteChildren(item: item, children: child)
}, label: {
Image(systemName: "star.circle.fill")
.font(.title3)
})
.tint(.green)
})
}
}
.listStyle(.insetGrouped)
.padding(.top, -10)
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
.autocapitalization(.none)
}
}
.navigationBarTitle("\(item.group!)'s Words")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
HStack {
Button(action: {
addChildren()
}, label: {
Image(systemName: "plus")
})
Button(action: {
itemModel.shuffleChildren(item: item)
}, label: {
Image(systemName: "shuffle")
})
NavigationLink(destination: FlashCardView(item: item)) {
Image(systemName: "play.rectangle")
}
.disabled(item.children.isEmpty)
})
}
}
}
extension AddChildrenView {
func addChildren() {
let alert = UIAlertController(title: "Saving word", message: "Type word and meaning 😀", preferredStyle: .alert)
alert.addTextField { word in
word.placeholder = "Type a word"
word.keyboardType = .alphabet
}
alert.addTextField { meaning in
meaning.placeholder = "a meaning of word"
}
let addfolderAction = UIAlertAction(title: "Add", style: .default, handler: {
(_) in
self.word = alert.textFields![0].text!
self.meaning = alert.textFields![1].text!
itemModel.addNewChildren(item: item, word: word, meaning: meaning)
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: {
(_) in
})
alert.addAction(cancelAction)
alert.addAction(addfolderAction)
UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
Hello, first, this view is the second view after click the navigationLink in firstView.
init() {
UINavigationBar.appearance().titleTextAttributes = [.font : UIFont(name: "NotoSans-Bold", size: 20)!]
}
I tried this code to use custom font on my navigationBartitle, but there is error like this.
"Return from initializer without initializing all stored properties"
Could you let me know the way I can solve this problem?
Thank you!

Related

SwiftUi: scrolling to last received message

Whenever a message is received from the backend, the app should automatically scroll to the beginning of the message. I cannot find a way for this to work. I've tried the stackoverflow solutions here, here and here to no avail.
Here's my code
struct ContentView: View {
#State private var inputText = ""
#State private var messages: [Message] = []
#State private var showThumbsDownSheet = false
#State private var feedbackText = ""
#State private var showThanksForFeedback = false
var body: some View {
VStack {
NavigationView {
ScrollViewReader { scrollView in
List {
ForEach(messages, id: \.id) { message in
Text(message.text)
if !message.isFromUser && message.isReplyFromBackEnd {
HStack {
ThumbsUpButton(message: message)
Spacer()
ThumbsDownButton(message: message, showThumbsDownSheet: $showThumbsDownSheet, reviewText: $feedbackText)
}
}
}
}
.onChange(of: messages) { value in
scrollView.scrollTo(self.messages.count - 1)
}
}
.navigationTitle("Ask Tais")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Menu {
Button(action: { exit(0) }) {
Label("Quit app", systemImage: "")
}
}
label: {
Label("Add", systemImage: "ellipsis.circle")
}
}
}
}
.onAppear {
// Add a "Hello" message to the list of messages when the app is first opened
self.messages.append(Message(id: UUID(), text: "Hello!", isFromUser: false, isReplyFromBackEnd: false, responseId: "0"))
}
.frame(maxHeight: .infinity)
HStack {
TextField("What do you want to say?", text: $inputText)
Button(action: {
self.messages.append(Message(id: UUID(), text: self.inputText, isFromUser: true, isReplyFromBackEnd: false, responseId:"0"))
sendRequest(with: self.inputText) { responseText, id in
self.messages.append(Message(id: UUID(), text: responseText, isFromUser: false, isReplyFromBackEnd: true, responseId: id))
}
self.inputText = ""
}) {
Text("Ask Tais")
}
}
.padding()
}
}
}
ScrollViewReader and scrollTo does not work on List with changing content. You have to use ScrollView instead of List and build your own list design.

Updating state variable does not update TextField's text

I have TextField in a form which should be updated dynamically, on a specific user action. Since I'm embedding this SwiftUI view inside a UIKit view, I had to customize it a bit:
/// Base TextField
struct CustomTextFieldSUI: View {
#State var text: String = ""
var placeholder: Text
var disableAutoCorrect = false
var isSecure: Bool = false
var onEditingChanged: (Bool) -> () = { _ in }
var onCommit: () -> () = { }
var onChange: (String) -> Void
var keyboardType: UIKeyboardType = .default
var contentType: UITextContentType?
var body: some View {
ZStack(alignment: .leading) {
if text.isEmpty {
placeholder
.foregroundColor(placeholderColor)
}
if isSecure {
SecureField("", text: $text.onChange({ newText in
onChange(newText)
}), onCommit: onCommit)
.foregroundColor(foregroundColor)
} else {
TextField("", text: $text.onChange({ newText in
onChange(newText)
}), onEditingChanged: onEditingChanged, onCommit: onCommit)
.foregroundColor(foregroundColor)
.disableAutocorrection(disableAutoCorrect)
.keyboardType(keyboardType)
.textContentType(contentType)
.autocapitalization(keyboardType == UIKeyboardType.emailAddress ? .none : .words)
}
}
}
}
Which itself is used to create another custom view:
struct TextFieldWithIconSUI: View {
#State var text: String = ""
#State var isSecure: Bool
let icon: Image
let placeholder: String
var prompt: String? = nil
let disableAutoCorrect: Bool = false
var keyboardType: UIKeyboardType = .default
var contentType: UITextContentType?
var onEditingChanged: (Bool) -> () = { _ in }
var onCommit: () -> () = { }
var onChange: (String) -> Void
var body: some View {
VStack {
ZStack {
RoundedRectangle(cornerRadius: 0)
HStack(alignment: .center, spacing: iconSpacing) {
CustomTextFieldSUI(
text: text,
placeholder: Text(placeholder),
placeholderColor: .gray,
foregroundColor: .white,
disableAutoCorrect: disableAutoCorrect,
isSecure: isSecure, onEditingChanged: onEditingChanged, onCommit: onCommit, onChange: { newText in
text = newText
onChange(newText)
},
keyboardType: keyboardType,
contentType: contentType)
icon
.scaledToFit()
.onTapGesture {
isSecure.toggle()
}
}
}
if (prompt != nil) {
VStack(alignment: .leading) {
Text(prompt!)
.padding(.leading, 10)
.padding(.top, -2)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(2)
}
}
}
}
}
Now in the client view I'm using it with a list to create a simple auto-complete list:
TextFieldWithIconSUI(text: displayCountryName, isSecure: false, icon: emptyImage, placeholder: "Country", keyboardType: .default, contentType: .countryName, onEditingChanged: { changed in
self.shouldShowCountriesList = changed
}, onChange: { text in
self.displayCountryName = text
self.shouldShowCountriesList = true
})
And somewhere in my form, I'm using the text from that TextField to show the autocorrect list. The list is shown but when I select an item it does not update the TextField's text, in debugger the state variable is updated, but the UI is not showing the new value.
if shouldShowCountriesList {
VStack(alignment: .leading) {
List {
ForEach(countriesList.filter { $0.lowercased().hasPrefix(country.object.lowercased()) }.prefix(3), id: \.self) { countryName in
Text(countryName)
.onTapGesture {
self.displayCountryName = countryName
self.shouldShowCountriesList = false
}
}
}
}
}
I had to use Binding variables for my custom text field:
struct CustomTextFieldSUI: View {
var text: Binding<String>
var placeholder: Text
var placeholderColor: Color
var foregroundColor: Color
var disableAutoCorrect = false
var isSecure: Bool = false
var onEditingChanged: (Bool) -> () = { _ in }
var onCommit: () -> () = { }
var onChange: (String) -> Void
var keyboardType: UIKeyboardType = .default
var contentType: UITextContentType?
var body: some View {
let bindingText = Binding(
get: {
self.text.wrappedValue
},
set: {
self.text.wrappedValue = $0
onChange($0)
})
ZStack(alignment: .leading) {
if text.wrappedValue.isEmpty {
placeholder
.foregroundColor(placeholderColor)
}
if isSecure {
SecureField("", text: bindingText, onCommit: onCommit)
.foregroundColor(foregroundColor)
} else {
TextField("", text: bindingText, onEditingChanged: onEditingChanged, onCommit: onCommit)
.foregroundColor(foregroundColor)
.disableAutocorrection(disableAutoCorrect)
.keyboardType(keyboardType)
.textContentType(contentType)
.autocapitalization(keyboardType == UIKeyboardType.emailAddress ? .none : .words)
}
}
}
}
And passed the binding variables from the host.

SwiftUI : I can't make ZStack view in NavigationLink's Screen

This is my NavigationLink DetailView,
import SwiftUI
struct AddChildrenView: View {
#State var word : String = ""
#State var meaning : String = ""
#State var isShowAlert : Bool = false
#State var isClicked : Bool = false
#EnvironmentObject var itemModel : ItemModel
var item : Item
var body: some View {
ZStack {
if item.children.count == 0 {
NochildrenView()
}
List {
ForEach(item.children) { child in
WordListRowView(children: child)
.onTapGesture {
withAnimation(.linear(duration: 0.3)) {
itemModel.favoriteChildren(item: item, children: child)
}
}
}
.onDelete { indexSet in
itemModel.deleteChildren1(item: item, indexSet: indexSet)
}
}
.id(UUID())
}
.navigationBarTitle("\(item.group!)'s Words")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
HStack {
Button(action: {
addChildren()
}, label: {
Image(systemName: "plus")
})
Button(action: {
itemModel.shuffleChildren(item: item)
}, label: {
Image(systemName: "shuffle")
})
NavigationLink(destination: FlashCardView(item: item)) {
Image(systemName: "play.rectangle")
}
.disabled(item.children.isEmpty)
})
}
}
extension AddChildrenView {
func addChildren() {
let alert = UIAlertController(title: "Saving word", message: "Type word and meaning 😀", preferredStyle: .alert)
alert.addTextField { word in
word.placeholder = "Type a word"
}
alert.addTextField { meaning in
meaning.placeholder = "a meaning of word"
}
let addfolderAction = UIAlertAction(title: "Add", style: .default, handler: {
(_) in
self.word = alert.textFields![0].text!
self.meaning = alert.textFields![1].text!
itemModel.addNewChildren(item: item, word: word, meaning: meaning)
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: {
(_) in
})
alert.addAction(addfolderAction)
alert.addAction(cancelAction)
UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
This is MotherView with NavigationLink,
import SwiftUI
struct HomeView: View {
#State var folderName : String = ""
#EnvironmentObject var itemModel : ItemModel
var body: some View {
NavigationView{
ZStack {
if itemModel.items.count == 0 {
NoItemView()
}
List{
ForEach(itemModel.items) {item in
if let group = item.group {
NavigationLink(destination: {
AddChildrenView(item : item)
}, label: {
HStack{
Image(systemName: "folder")
.foregroundColor(.yellow)
Text(group)
.font(.headline)
.lineLimit(1)
}
})
}
}
.onMove(perform: itemModel.moveItem)
.onDelete(perform: itemModel.deleteItem)
}
.listStyle(.plain)
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationTitle("SelfDic 📒")
.navigationBarItems(trailing:
Button(action: {
addFolderView()
}, label: {
Image(systemName: "folder.badge.plus")
}))
.navigationBarItems(leading: EditButton())
}
}
}
extension HomeView {
func addFolderView() {
let alert = UIAlertController(title: "Saving folder", message: "Type a name of folder 🙂", preferredStyle: .alert)
alert.addTextField { name in
name.placeholder = "folder's name"
}
let addfolderAction = UIAlertAction(title: "Add", style: .default, handler: {
(_) in
self.folderName = alert.textFields![0].text!
itemModel.addNewFolder(text: folderName)
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: {
(_) in
})
alert.addAction(addfolderAction)
alert.addAction(cancelAction)
UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
**I wanted to use Zstack in my linked View
The logic is that, If there is no item.children, I will show NoChildrenView().
But, It doesn't work.
How can I treat this problem?**
I hope I can solve this!
Thank you all senior developers!!
I didn't fully check all code, but in AddChildrenView there might be missing an else. Like you have it now the empty list will be always shown above your NochildrenView()
if item.children.count == 0 {
NochildrenView()
} else { // here
...

Swiftui: How to reset a Bool State?

When I tap edit, it will show a delete button (minus icon). When the delete button tapped it will show the orange delete option (as the gif shows). Now I'm trying to reset the state back to the origin (from orange button back to video name and length ) when the Done button is tapped.
I'm trying few options like closures but nothing much.
Any help would be much appreciated!
My child view Video
struct Video: View {
var videoImage : String
var title : String
var duaration : Int
#Binding var deleteActivated : Bool
var body: some View {
HStack {
Image(videoImage)
...
if deleteActivated {
Button(action: {
}) {
ZStack {
Rectangle()
.foregroundColor(.orange)
.cornerRadius(radius: 10, corners: [.topRight, .bottomRight])
Text("Delete")
.foregroundColor(.white)
}
}
} else {
VStack(alignment: .leading){
....
My parent view VideosDirectory
struct VideosDirectory: View {
#State var videos:[DraftVideos] = [
DraftVideos(isSelected: true,title: "Superman workout", duration: 5, imageURL: "test"),
DraftVideos(isSelected: true,title: "Ironman workout", duration: 15, imageURL: "test1"),
DraftVideos(isSelected: true,title: "Ohman workout and long name", duration: 522, imageURL: "test2")
]
init() {
self._deleteActivated = State(initialValue: Array(repeating: false, count: videos.count))
}
#State private var deleteActivated: [Bool] = []
#State private var show = false
// #State private var editing = false
var body: some View {
// VStack {
NavigationView {
ScrollView(.vertical) {
VStack {
ForEach(videos.indices, id: \.self) { i in
HStack {
if self.show {
Button(action: {
withAnimation {
self.deleteActivated[i].toggle()
}
}) {
Image(systemName: "minus.circle.fill")
...
}
}
Video(videoImage: videos[i].imageURL, title: videos[i].title, duaration: videos[i].duration, deleteActivated: $deleteActivated[i])
}
}
}
.animation(.spring())
}
.navigationBarItems(trailing:
HStack {
Button(action: {
self.show.toggle()
}) {
if self.show {
Text("Done")
} else {
Text("Edit")
}
}
})
}
}
}
Provided code is not testable so just an idea:
Button(action: {
self.deleteActivated = Array(repeating: false, count: videos.count)
self.show.toggle()
}) {
or almost the same but as "post-action" in
}
.animation(.spring())
.onChange(of: self.show) { _ in
// most probably condition is not needed here, but is up to you
self.deleteActivated = Array(repeating: false, count: videos.count)
}

Why won't my bound [String] not change my multi-select list SwiftUI

I am trying to create a multi-select list:
#Binding var selection:[String]
List {
ForEach(self.items, id: \.self) { item in
MultipleSelectionRow(title: item, isSelected: self.selection.contains(item)) {
if self.selection.contains(item) {
self.selection.removeAll(where: { $0 == item }) <=== NO AFFECT
}
else {
self.selection.append(item). <=== NO AFFECT
}
self.queryCallback()
}
}//ForEach
.listRowBackground(Color("TPDarkGrey"))
}//list
I have a row which is a button that calls the above action
struct MultipleSelectionRow: View {
var title: String
var isSelected: Bool
var action: () -> Void
var body: some View {
Button(action: self.action) {
HStack {
Text(self.title)
Spacer()
if self.isSelected {
Image(systemName: "checkmark")
}
}
.font(.system(size: 14))
}
}
}
Why does it not append or remote the item in the bound array? It seems to change on the second time through the view
I managed to produce an example from your code that works:
I don't know how the rest of your code is setup so I cannot hint you to anything unfortunately.
struct MultipleSelectionRow: View {
var title: String
var isSelected: Bool
var action: () -> Void
var body: some View {
Button(action: self.action) {
HStack {
Text(self.title)
Spacer()
if self.isSelected {
Image(systemName: "checkmark")
}
}
.font(.system(size: 14))
}
}
}
struct ContentView: View {
#State var selection:[String] = []
#State var items:[String] = ["Hello", "my", "friend", "did", "I", "solve", "your", "question", "?"]
var body: some View {
List {
ForEach(self.items, id: \.self) { item in
MultipleSelectionRow(title: item, isSelected: self.selection.contains(item)) {
if self.selection.contains(item) {
self.selection.removeAll(where: { $0 == item })
}
else {
self.selection.append(item)
}
}
}
.listRowBackground(Color("TPDarkGrey"))
}
}
}
I hope this helps to clarify things.