How do I vertically center my content on my swiftui app? - ios

I'm a beginner here, and just trying to put together a simple form and button (right now they don't do anything). For some reason I can't seem to move the form -- which is just going to be one text input by the way, so open to using a different method other than form -- to be centered vertically. Can anyone help me diagnose the problem? Here is my code:
import SwiftUI
struct CreateTopic: View {
var body: some View {
VStack {
Text("Create Topic")
.font(.largeTitle)
Spacer() // <-- Add a Spacer view above the Form view
Form {
TextField("Name", text: $name)
// TextField("Email", text: $email)
// TextField("Phone", text: $phone)
}
.frame(height: 100)
Spacer() // <-- Add a Spacer view below the Form view
NavigationView {
StandardButton(text: "Test", action: {})
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
#State private var name = ""
// #State private var email = ""
// #State private var phone = ""
}
struct CreateTopic_Previews: PreviewProvider {
static var previews: some View {
CreateTopic()
}
}
I tried adding that Spacer() above the Form, which didn't really do anything. It did however move the button.
Should I not be using a Form if I am only looking to use one simple text input?

There are multiple misunderstandings here. We can quickly solve them.
Form is a scrollable form. It is typically meant to encompass the entire screen. And if you use a form, you will not be able to center its content. Forms always lay out their subviews top-to-bottom. You could center a form within a larger view, but I wouldn't. I ran your code and you can scroll the form within the 100pt-tall rectangle. It's just weird.
NavigationView is meant to wrap the entire screen, not just a single button. It does things like add a navigation bar, display a title, and support buttons in the corners of the screen. It also supports the standard in-and-out screen transitions across all of iOS. Think of the Messages app and what happens when you tap into a conversation. NavigationView does that stuff.
With these two clarifications in mind, I would rewrite your view one of two ways.
Option 1, using a Form
struct CreateTopic: View {
var body: some View {
NavigationView {
Form {
Section {
TextField("Name", text: $name)
.textContentType(.name)
TextField("Email", text: $email)
.textContentType(.emailAddress)
TextField("Phone", text: $phone)
.textContentType(.telephoneNumber)
.keyboardType(.phonePad)
}
Section {
Button("Create") {
// Create the topic
}
}
}
.navigationTitle("Create Topic")
}
}
#State private var name = ""
#State private var email = ""
#State private var phone = ""
}
Option 2, without a Form, centering your text fields like you wanted
struct CreateTopic: View {
var body: some View {
NavigationView {
VStack {
TextField("Name", text: $name)
.textContentType(.name)
TextField("Email", text: $email)
.textContentType(.emailAddress)
TextField("Phone", text: $phone)
.textContentType(.telephoneNumber)
.keyboardType(.phonePad)
Button("Create") {
// Create the topic
}
}
.textFieldStyle(.roundedBorder)
.padding()
.frame(maxHeight: .infinity)
.background(Color(.systemGroupedBackground))
.navigationTitle("Create Topic")
}
}
#State private var name = ""
#State private var email = ""
#State private var phone = ""
}
Form fits the style of iOS more, but the second option looks fine and could be what you wanted.

Related

Disable keyboard avoidance in a bottom sheet

TLDR: The view modifier .ignoresSafeArea(.keyboard) does not appear to work when used inside a bottom sheet. Is there a workaround?
In a SwiftUI View, tapping a TextField invokes the keyboard and the Textfield then moves upwards to avoid the keyboard.
struct ContentView: View {
#State var mytext: String = "Some text"
var body: some View {
VStack {
Spacer()
TextField("abc", text: $mytext)
Spacer()
}
}
}
This keyboard avoidance behaviour can be disabled by adding the .ignoresSafeArea modifier
struct ContentView: View {
#State var mytext: String = "Some text"
var body: some View {
VStack {
Spacer()
TextField("abc", text: $mytext)
Spacer()
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
and the TextField no longer moves upwards.
If this technique is applied inside to a view in a bottom sheet it no longer works and the entire sheet is pushed up by the keyboard.
struct ContentView: View {
#State var mytext: String = "Some text"
#State var isPresented: Bool = true
var body: some View {
Color.mint
.sheet(isPresented: $isPresented) {
VStack {
Spacer()
TextField("abc", text: $mytext)
Spacer()
}
.presentationDetents( [.fraction(0.33)] )
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
}
I've tried applying .ignoresSafeArea(.keyboard, edges: .bottom) to every view thats exposed in the code with no success.
I suspect that the bug is due to the bottom sheet implementation using a UIHostingController internally. This can been seen using Xcode's Debug View Hierarchy tool.
Others have described how UIHostingController does not respect the .ignoresSafeArea(.keyboard, edges: .bottom) modifier and have developed workarounds but these are not applicable here because the UIHostingController is created internally, not explicitly in my code.
Is there any way to get the view inside the sheet to ignore the keyboard and stay put?
I'm open to any and all suggestions. Thanks!

SwiftUI Disclosure Group Text Alignment

Is there a way to prevent wrapped text in a DisclouseGroup title from being center aligned?
I have tried to add the following, but neither approach has been effective:
DisclosureGroup("A really long disclosure group title that is being center aligned.", isExpanded: false) {
...
}
.multilineTextAlignment(.leading)
DisclosureGroup("A really long disclosure group title that is being center aligned.", isExpanded: false) {
...
}
.frame(maxWidth: .infinity, alignment: .leading)
You can use the Label initialiser for DisclosureGroup. You can read more about it here
Here is a working example.
struct ContentView: View {
#State private var isExpanded = false
var body: some View {
VStack {
DisclosureGroup(isExpanded: $isExpanded) {
Text("This is some text")
} label: {
Text("A really long disclosure group title that is being center aligned.")
.multilineTextAlignment(.leading)
}
}
}
}
This gives the following output
Tested Xcode 14 beta 4 iOS 16 Simulator
You can wrap it in a Form or a List:
struct DisclosureAligning: View {
#State var isExpanded = false
var body: some View {
Form {
DisclosureGroup("A really long disclosure group title that is being center aligned.", isExpanded: $isExpanded) {
Text("text")
}
}
}
}

I want to create a view where the hashtag is displayed in SwiftUI

Currently, I am studying SwiftUI and making UI that is displayed in the view when registering hashtags.
But I don't know what to do with the logic of creating and inserting a new HStack when it's out of screen size inside the VStack.
I've searched several times, but I couldn't find any helpful words or keywords.
I would appreciate it if you could help me by knowing the answer.
The image above is an example.
I want to create a new HStack and put it in the VStack when the width exceeds the horizontal size of the device while inserting a text item into the HStack.
struct HashTagView: View {
var hashTagArray: [String] = ["#Lorem", "#Ipsum", "#dolor", "#sit", "#amet", "#consectetur", "#adipiscing", "#elit", "#Nam", "#semper", "#sit", "#amet", "#ut", "#eleifend", "#Cras"]
var body: some View {
VStack(spacing: 5) {
ForEach(hashTagArray, id:\.self) { tag in
Text(tag)
}
}
.padding()
.border(Color.blue)
.frame(width: UIScreen.main.bounds.size.width)
}
}
this is my code.
You should use a LazyVgrid with an adaptive layout here:
I removed some entries from your array because if you use it with id: \.self you should ensure that every entry is unique.
Documentation
struct HashTagView: View {
var hashTagArray: [String] = ["#Lorem", "#Ipsum", "#dolor", "#consectetur", "#adipiscing", "#elit", "#Nam", "#semper", "#sit", "#amet", "#ut", "#eleifend", "#Cras"]
private var gridItemLayout = [GridItem(.adaptive(minimum: 100))]
var body: some View {
ScrollView{
LazyVGrid(columns: gridItemLayout , spacing: 5) {
ForEach(hashTagArray, id:\.self) { tag in
Text(tag)
}
}
.padding()
.border(Color.blue)
}
.frame(width: UIScreen.main.bounds.size.width)
}
}

Swap between views on button press - SwiftUI

I am a beginner to swift, and i want to swap between 2 views.
Here is my current code.
I have tried to look at other stackoverflow questions but I dont understand what a viewcontroller or anything is, so if you do answer please explain what it does.
All i want to do is swap from ContentView to MessageView when the sign up button is pressed, but I don't know how to approach this at all.
import SwiftUI
struct ContentView: View {
#State private var username: String = ""
#State private var password: String = ""
var body: some View {
VStack {
Text("Create an account.")
TextField("Username", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 250, height: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/)
SecureField("Password", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 250, height: /*#START_MENU_TOKEN#*/100/*#END_MENU_TOKEN#*/)
let button = Button("Sign Up!") {
if(username == "" || password == "") {
print("no suername or password")
} else {
print("user", username)
print("password", password)
}
}
button.cornerRadius(5.0)
}
}
}
struct MessageView: View {
var body: some View {
NavigationView {
List {
Text("FortniteGamer123")
Text("FortniteEpicKid")
Text("KidIsGoodAtFortnite")
}
}.navigationTitle("Fortnite Gamers")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If anyone could at least give me some help on view controllers and how to use them that would be great.
You're using SwiftUI, which doesn't really have a concept of view controllers (or at least UIViewController), which are more part of UIKit.
SwiftUI, rather, is made up of sets of composable views. You already have a couple that you've defined (like ContentView and MessageView) and are using a bunch of built-in SwiftUI views (like TextField, Text, Button, etc).
To swap between views, you can use an if statement that is dependent on a #State variable.
I refactored things a bit so that LoginView and MessageView are shown by a parent view (ContentView) depending on the state of loggedIn. This is passed via a Binding to LoginView so that when the button is pressed, the value can get passed back up to the parent.
struct ContentView: View { //parent view
#State var loggedIn = false //state variable
var body: some View {
if loggedIn { //'if' condition
MessageView()
} else {
LoginView(loggedIn: $loggedIn) //passing loggedIn with the $ sign makes it a two-way binding where the child view can now have access to and change the variable
}
}
}
struct LoginView : View {
#Binding var loggedIn : Bool //this is how loggedIn gets passed in
#State private var username: String = ""
#State private var password: String = ""
var body: some View {
VStack {
Text("Create an account.")
TextField("Username", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 250, height: 100)
SecureField("Password", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 250, height: 100)
Button(action: {
if(username == "" || password == "") {
print("no suername or password")
} else {
print("user", username)
print("password", password)
loggedIn = true //set the binding to true, which will effect the parent view
}
}) {
Text("Sign Up!")
}
.padding()
.overlay(RoundedRectangle(cornerRadius: 5.0).stroke(Color.accentColor)) //using this to round the corners
}
}
}
Technically, you could've also approached this by using NavigationView and NavigationLink, but that would've resulted in a situation where the user could've tapped the back button to go back to the login screen, which isn't really something you see in iOS, so I took your title literally about swapping between the two views.
I made a couple very minor edits to your other code. For example, had a let Button = in the middle of your view hierarchy that I've adjusted -- normally in SwiftUI you won't be defining variables that capture the views -- instead, you'll just be defining them in the hierarchy.
In Swift we have two main framework to build iOS applications: UIKit and SwiftUI. The first one is imperative and the second one - declarative. In UIKit we are using controllers and views classes to create UI and in SwiftUI we are using View structures to declare how we want user interface to show up. Basically SwiftUI in this case is similar to React / React Native and Flutter. In you example you are using SwiftUI, so you need to declare state variable and show the view based on this variable value, for example using .sheet(isPresented: <your state var>) or if statement. Also you could use NavigationView and Navigation Link.

SwiftUI 2 questions but very simple, about searchBar and the backgroundImage for List

SwiftUI very simple 2 questions
1, how to make the searchBar stick to top so that when use scroll down they can still see they searchBar
2, I'm using a List and work with a TabView and I want the List or the VStack maybe(or something that is holding the List) to have a background Image(a png already in my Assets.xcassets). I tried ZStack or .background(Image("...")) but none of them worked.
Here is the image, I want the background to be as the teal color png image.
Here is the code of one of the view for TabView:
struct Discover: View {
#State private var keywords: String = ""
var names = ["Yu Song", "ZhangYuan", "Kotoyama"]
var body: some View {
ZStack {
List {
HStack {
SearchBar(text: $keywords)
Image(systemName: "person")
Image(systemName: "bell")
}
ForEach(self.names.filter {
self.keywords.isEmpty ? true : $0.localizedCaseInsensitiveContains(self.keywords)
}, id: \.self) { name in
Text(name)
}
}.background(Image("bg_global"))
}.edgesIgnoringSafeArea(.all)
}
}
here is the preview for your convinence:
Notice that I already added .background(Image("bg_global")) in List and VSrack but the image never showed.
Thanks a lot.
To make background visible it's needed to make List and its content transparent. It can be done for example in init,
init() {
UITableView.appearance().backgroundColor = .clear
UITableViewCell.appearance().backgroundColor = .clear
}
Use a VStack instead of a ZStack to make the SearchBar stick to the top. And make sure the SearchBar is not part of the List View.
struct ContentView: View {
init() {
UITableView.appearance().backgroundColor = .clear
UITableViewCell.appearance().backgroundColor = .clear
}
#State private var keywords: String = ""
var names = ["Yu Song", "ZhangYuan", "Kotoyama"]
var body: some View {
VStack{
// Your Search Bar View
HStack{
Spacer()
Text("Search")
Spacer()
}.padding().background(Color.red)
List {
ForEach(self.names.filter {
self.keywords.isEmpty ? true : $0.localizedCaseInsensitiveContains(self.keywords)
}, id: \.self) { name in
Text(name)
}
}
}.offset(x: 0, y: 45).background(Color.yellow).edgesIgnoringSafeArea(.all)
}
}
Since you also want to show the background in the safe areas you need to push down the view with offset.

Resources