I'm figuring out how to make views adaptable to different screen sizes in SwiftUI. Currently, I'm trying to create a register screen that should have a certain maximum width, but still have a padding if the maximum width can't be applied on small screen sizes.
I'm using a GeometryReader at the root to retrieve the width of the view and to apply it to the "Register" button. So I tried adding a padding to the GeometryReader, but without success. The reason is, that you can set the maxWidth on the GeometryReader doesn't work, it gets wider than the screen size.
My code looks like this:
struct RegisterPage: View {
#State private var email: String = ""
#State private var username: String = ""
#State private var password: String = ""
var body: some View {
GeometryReader { geometry in
VStack {
TextField("login.email_placeholder", text: self.$email)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.bottom)
TextField("login.username_placeholder", text: self.$username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.bottom)
TextField("login.password_placeholder", text: self.$password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.bottom)
Button(
action: {},
label: {
Text("login.register_button")
.frame(width: geometry.size.width)
.padding()
.background(Color.blue)
.foregroundColor(Color.white)
.cornerRadius(5)
}
)
}
}
.frame(maxWidth: 500)
}
}
If I got what you're trying to do, you can just use the padding modifier on the GeometryReader:
struct RegisterPage: View {
#State private var email: String = ""
#State private var username: String = ""
#State private var password: String = ""
var body: some View {
GeometryReader { geometry in
VStack {
TextField("login.email_placeholder", text: self.$email)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.bottom)
TextField("login.username_placeholder", text: self.$username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.bottom)
TextField("login.password_placeholder", text: self.$password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.bottom)
Button(
action: {},
label: {
Text("login.register_button")
.frame(width: geometry.size.width)
.padding()
.background(Color.blue)
.foregroundColor(Color.white)
.cornerRadius(5)
}
)
}
}
.frame(maxWidth: 500)
.padding(50)
}
}
EDIT: look the result with maxWidth = 10000
Related
so I am trying to have a view update to display a custom view based on a user selection from another view. This is a simple task app project I started to get a better understanding of SwiftUI and have hit my first major roadblock. The custom view is generated from a Tag object from Core Data, so it would be this information that is passed from View 2 to View 1.
I've marked where the update would take place as well as where the action is performed with TODOs. Hopefully I did a good job at explaining what I am hoping to accomplish, nothing I have tried seems to work. I am sure it's something simple but the solution is evading me.
View 1: View that needs to be updated when user returns
View 2: View where selection is made
The View that needs to be updated and its ViewModel.
struct AddTaskView: View {
//MARK: Variables
#Environment(\.managedObjectContext) var coreDataHandler
#Environment(\.presentationMode) var presentationMode
#StateObject var viewModel = AddTaskViewModel()
#StateObject var taskListViewModel = TaskListViewModel()
#State private var title: String = ""
#State private var info: String = ""
#State private var dueDate = Date()
var screenWidth = UIScreen.main.bounds.size.width
var screenHeight = UIScreen.main.bounds.size.height
var body: some View {
VStack(spacing: 20) {
Text("Add a New Task")
.font(.title)
.fontWeight(.bold)
//MARK: Task.title Field
TextField("Task", text: $title)
.font(.headline)
.padding(.leading)
.frame(height: 55)
//TODO: Update to a specific color
.background(Color(red: 0.9, green: 0.9, blue: 0.9))
.cornerRadius(10)
//MARK: Task.tag Field
HStack {
Text("Tag")
Spacer()
//TODO: UPDATE TO DISPLAY TAG IF SELECTED OTHERWISE DISPLAY ADDTAGBUTTONVIEW
NavigationLink(
destination: TagListView(),
label: {
AddTagButtonView()
}
)
.accentColor(.black)
}
//MARK: Task.info Field
TextEditor(text: $info)
.frame(width: screenWidth - 40, height: screenHeight/4, alignment: .center)
.autocapitalization(.sentences)
.multilineTextAlignment(.leading)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.black, lineWidth: 0.5)
)
//MARK: Task.dateDue Field
DatePicker(
"Due Date",
selection: $dueDate,
in: Date()...
)
.accentColor(.black)
.font(.headline)
Spacer()
Button(action: {
viewModel.addTask(taskTitle: title, taskInfo: info, taskDueDate: dueDate)
//Dismiss View if successful
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Add Task")
.frame(width: 150, height: 60)
.font(.headline)
.foregroundColor(.black)
.background(Color.yellow)
.cornerRadius(30)
})
}
.padding()
.navigationBarTitleDisplayMode(.inline)
}
}
final class AddTaskViewModel : ObservableObject {
var coreDataHandler = CoreDataHandler.shared
#Published var tag : Tag?
func addTask(taskTitle: String, taskInfo: String, taskDueDate: Date) {
let newTask = Task(context: coreDataHandler.container.viewContext)
newTask.title = taskTitle
newTask.info = taskInfo
newTask.dateCreated = Date()
newTask.dateDue = taskDueDate
newTask.completed = false
newTask.archived = false
coreDataHandler.save()
}
}
The View where the selection is made and its ViewModel
struct TagListView: View {
#FetchRequest(entity: Tag.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Tag.title, ascending: true)]) var tagList : FetchedResults<Tag>
#Environment(\.presentationMode) var presentationMode
#StateObject var viewModel = TagListViewModel()
var body: some View {
VStack {
HStack {
Text("Create a Tag")
.font(.system(size: 20))
.fontWeight(.medium)
Spacer()
NavigationLink(
destination: CreateTagView(),
label: {
Image(systemName: "plus.circle")
.font(.system(size: 25))
})
}
Divider()
.padding(.bottom, 10)
ScrollView(.vertical, showsIndicators: false, content: {
if tagList.count != 0 {
LazyVStack(spacing: 20) {
ForEach(tagList, id: \.self) { tag in
let tagColour = Color(red: tag.colourR, green: tag.colourG, blue: tag.colourB, opacity: tag.colourA)
Button {
//TODO: UPDATE ADDTASKVIEW TO DISPLAY THE SELECTED TAG
//Dismiss view
self.presentationMode.wrappedValue.dismiss()
} label: {
TagView(title: tag.title ?? "Tag", color: tagColour, darkText: false)
}
}
}
} else {
Text("Add your first tag.")
}
})
}
.padding()
}
}
final class TagListViewModel : ObservableObject {
}
So i want my keyboard to overlay the view so that the view stays and not going upwards. i did several variations such as adding it in my loginstuff, or adding in it navigationView. it doesn't work at all
Here's my code
struct LoginView: View {
#StateObject var userData = UserData()
var body: some View {
NavigationView {
ZStack(alignment:.top) {
Color.pink.ignoresSafeArea(edges: .top)
VStack {
Image(systemName: "graduationcap.fill")
.resizable()
.scaledToFit()
.frame(width: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, height: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.foregroundColor(.white)
.padding(.top,30)
Text("Study +")
.font(.title)
.fontWeight(.medium)
.foregroundColor(.white)
Spacer()
//Mark : The login Thinggy
LoginStuffs()
}
}
.edgesIgnoringSafeArea(.bottom)
.navigationTitle("Login")
.navigationBarHidden(true)
}
}
}
Login Stuff
struct LoginStuffs: View {
#State var username:String = ""
#State var password:String = ""
#State var isShow:Bool = false
var body: some View {
Vstack{
Textfield()
Securefield()
Securefield()
}
.padding()
.frame(width:UIScreen.width,height:UIScreen.height/1.5)
.background(Color.white)
.cornerRadius(15, corners: [.topLeft, .topRight])
//.ignoresSafeArea(edges: /*#START_MENU_TOKEN#*/.bottom/*#END_MENU_TOKEN#*/)
}
}
Seems like there's a problem within my codes in which I did not know (probably due to not learn it properly). please do help, thank you for your attention
use on your NavigationView
.edgesIgnoringSafeArea(.bottom))
Overview of the issue
I am building a note taking app and there is a note editing view where a few TextEditors are used to listen to users' input. Right now an environment object is passed to the this note editing view and I designed a save button to save the change. It works well except that users have to click the save button to update the model.
Expected behaviors
The text editor is expected to update the value of instances of the EnvironmentObject once the editing is done. Do not necessarily click the save button to save the changes.
below is the sample code of view
struct NoteDetails: View {
#EnvironmentObject var UserData: Notes
#Binding var selectedNote: SingleNote?
#Binding var selectedFolder: String?
#Binding var noteEditingMode: Bool
#State var title:String = ""
#State var updatedDate:Date = Date()
#State var content: String = ""
var id: Int?
var label: String?
#State private var wordCount: Int = 0
var body: some View {
VStack(alignment: .leading) {
TextEditor(text: $title)
.font(.title2)
.padding(.top)
.padding(.horizontal)
.frame(width: UIScreen.main.bounds.width, height: 50, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
Text(updatedDate, style: .date)
.font(.subheadline)
.padding(.horizontal)
Divider()
TextEditor(text: $content)
.font(.body)
.lineSpacing(15)
.padding(.horizontal)
.frame(maxHeight:.infinity)
Spacer()
}
}
}
below is the sample code of edit func of the EnvironmentObject
UserData.editPost(label: label!, id: id!, data: SingleNote(updateDate: Date(), title: title, body: content))
Ways I have tried
I tried to add a onChange modifier to the TextEditor but it applies as soon as any change happens, which is not desired. I also tried a few other modifiers like onDisapper etc.
User data has #Published var NoteList: [String: [SingleNote]] and I tried to pass the $UserData.NoteList[label][id].title in to the TextEditor and it was not accepted either
Did not find sound solutions in the Internet so bring up this question here. Thanks for suggestions in advance!
I don't know exactly what you mean to save once the editing is done. Here are two possible approaches I found.
Note:
In the following demos, the text with blue background displays the saved text.
1. Saving when user dismisses keyboard
Solution: Adding a tap gesture to let users dismiss the keyboard when tapped outside of the TextEditor. Call save() at the same time.
Code:
struct ContentView: View {
#State private var text: String = ""
#State private var savedText: String = ""
var body: some View {
VStack(spacing: 20) {
Text(savedText)
.frame(width: 300, height: 200, alignment: .topLeading)
.background(Color.blue)
TextEditor(text: $text)
.frame(width: 300, height: 200)
.border(Color.black, width: 1)
.onTapGesture {}
}
.onTapGesture { hideKeyboardAndSave() }
}
private func hideKeyboardAndSave() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
save()
}
private func save() {
savedText = text
}
}
2. Saving after no changes for x seconds
Solution: Using Combine with .debounce to publish and observe only after x seconds have passed with no further events.
I have set x to 3.
Code:
struct ContentView: View {
#State private var text: String = ""
#State private var savedText: String = ""
let detector = PassthroughSubject<Void, Never>()
let publisher: AnyPublisher<Void, Never>
init() {
publisher = detector
.debounce(for: .seconds(3), scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}
var body: some View {
VStack(spacing: 20) {
Text(savedText)
.frame(width: 300, height: 200, alignment: .topLeading)
.background(Color.blue)
TextEditor(text: $text)
.frame(width: 300, height: 200)
.border(Color.black, width: 1)
.onChange(of: text) { _ in detector.send() }
.onReceive(publisher) { save() }
}
}
private func save() {
savedText = text
}
}
I am having trouble with the text in SwiftUI SegmentedPicker bouncing when I tap on the various segments.
This is super basic so I am not sure what options there are for adjusting this:
struct ContentView : View {
#State private var selectorIndex = 0
#State private var numbers = ["One","Two","Three"]
var body: some View {
VStack {
Picker("Numbers", selection: $selectorIndex) {
ForEach(0 ..< numbers.count) { index in
Text(self.numbers[index]).tag(index)
}
}
.pickerStyle(SegmentedPickerStyle())
Text("Selected value is: \(numbers[selectorIndex])").padding()
}
}
}
try adding this to the Text, especially the alignment:
.frame(width: 222, height: 55, alignment: .leading)
Edit:
I'm using the following code to test the text bounce on real devices and various simulators:
struct ContentView: View {
#State private var selectorIndex = 0
#State private var numbers = ["One","Two","Three"]
var body: some View {
VStack {
Picker("Numbers", selection: $selectorIndex) {
ForEach(0 ..< numbers.count) { index in
Text(self.numbers[index]).tag(index)
}
}
.pickerStyle(SegmentedPickerStyle())
Text("Selected value is: \(numbers[selectorIndex])")
.frame(width: 222, height: 55, alignment: .leading)
}
}
}
I was trying to make a custom list. And its acting weired if we add Encapsulated VStack in scrollView and try to add new row from that VStack. But we have to encapsulate because in Xcode will give "complex view complier error". I am providing full code for better understanding. Please try to run it. New element is not added as expected and its pushing everything upward.
struct RowView: View {
var body: some View {
VStack{
HStack{
Spacer()
.foregroundColor(Color.black)
Spacer()
}
}
.background(Color.white)
.cornerRadius(13)
.padding()
}}
struct cView:View {
#State var array: [String] = []
#State var height: CGFloat = 60
var body: some View {
VStack{
Button(action: {
self.array.append("Test")
}, label: {
Text("Add")
})
VStack{
ForEach(array, id: \.self){_ in
RowView()
}
}
.background(Color.red)
.cornerRadius(13)
.padding()
}
}}
struct ContentView : View {
#State var array: [String] = []
var body: some View {
ScrollView{
VStack{
Text("d")
.frame(height: 90)
VStack{
cView()
}
}
}
.navigationBarTitle("Test", displayMode: .automatic)
}}
When I reformatted and removed unused stuff I got:
struct RowView: View {
let text: String
var body: some View {
VStack{
HStack{
Spacer()
Text(text).foregroundColor(Color.black)
Spacer()
}
}
.background(Color.white)
.cornerRadius(13)
.padding()
}
}
struct cView:View {
#State var array: [String] = []
#State var height: CGFloat = 60
var body: some View {
VStack{
Button(
action: { self.array.append("Test") },
label: { Text("Add") }
)
ForEach(array, id: \.self){text in
RowView(text: text)
}
.background(Color.red)
.cornerRadius(13)
.padding()
}
}
}
struct ContentView : View {
var body: some View {
List {
VStack{
Text("d")
cView()
}
}
}
}
ScrollView is a real PITA and it hates Text which is why I replaced it with a List. RowView was missing a Text IMHO so I put one in. The array in ContentView was never used so I removed it, similarly a navigatinBarTitle needs a NavigationView.
This isn't really an answer as it uses List instead of ScrollView but it does point to where your problems lie. It is also very strange as every thing is in a single List row but I tried to change as little as possible.
You might like to try running SwiftLint on your code. I often swear at it, especially when it complains about the cyclomatic complexity of my enum switches but it does improve my code.
Most likely a bug, but I did not need to encapsulate. And if I don't, the code works as expected:
struct RowView: View {
var body: some View {
VStack{
HStack{
Spacer()
.foregroundColor(Color.black)
Spacer()
}
}
.background(Color.white)
.cornerRadius(13)
.padding()
}
}
struct ContentView : View {
#State var array: [String] = []
#State var height: CGFloat = 60
var body: some View {
ScrollView{
VStack{
Text("d")
.frame(height: 90)
VStack{
Button(action: {
self.array.append("Test")
}, label: {
Text("Add")
})
VStack{
ForEach(array, id: \.self){_ in
RowView()
}
}
.background(Color.red)
.cornerRadius(13)
.padding()
}
}
}
.navigationBarTitle("Test", displayMode: .automatic)
}
}