How to create View inside another view with SwiftUI? - ios

I need to make such a simple thing, but can't figure out how.
So I need to create a View which I already have inside another view. Here how it looks like now ↓
Here's my button:
struct CircleButton: View {
var body: some View {
Button(action: {
self
}, label: {
Text("+")
.font(.system(size: 42))
.frame(width: 57, height: 50)
.foregroundColor(Color.white)
.padding(.bottom, 7)
})
.background(Color(#colorLiteral(red: 0.4509803922, green: 0.8, blue: 0.5490196078, alpha: 1)))
.cornerRadius(50)
.padding()
.shadow(color: Color.black.opacity(0.15),
radius: 3,
x: 0,
y: 4)
}
}
Here's a view which I want to place when I tap the button ↓
struct IssueCardView: View {
var body: some View {
ZStack (alignment: .leading) {
Rectangle()
.fill(Color.white)
.frame(height: 50)
.shadow(color: .black, radius: 20, x: 0, y: 4)
.cornerRadius(8)
VStack (alignment: .leading) {
Rectangle()
.fill(Color(#colorLiteral(red: 0.6550863981, green: 0.8339114785, blue: 0.7129291892, alpha: 1)))
.frame(width: 30, height: 8)
.cornerRadius(8)
.padding(.horizontal, 10)
Text("Some text on card here")
.foregroundColor(Color(UIColor.dark.main))
.font(.system(size: 14))
.fontWeight(.regular)
.padding(.horizontal, 10)
}
}
}
}
Here's a view where I want to place this IssueCardView ↓. Instead of doing it manually like now I want to generate this View with button.
struct TaskListView: View {
var body: some View {
ScrollView(.vertical, showsIndicators: false, content: {
VStack (alignment: .leading, spacing: 8) {
**IssueCardView()
IssueCardView()
IssueCardView()
IssueCardView()
IssueCardView()**
}
.frame(minWidth: 320, maxWidth: 500, minHeight: 500, maxHeight: .infinity, alignment: .topLeading)
.padding(.horizontal, 0)
})
}
}

Although it works with scrollView and stack, You should use a List for these kind of UI issues (as you already mentioned in the name of TaskListView)
struct TaskListView: View {
typealias Issue = String // This is temporary, because I didn't have the original `Issue` type
#State var issues: [Issue] = ["Some issue", "Some issue"]
var body: some View {
ZStack {
List(issues, id: \.self) { issue in
IssueCardView()
}
CircleButton {
self.issues += ["new issue"]
}
}
}
}
I have added let action: ()->() to CircleButton. So I can pass the action to it.

Related

TextField stroke border text with SwiftUI

Here is what I've done, but the problem is with Text background. It can be implemented on white background by setting the Text's background to white as well, but in case of image background it stays "strikedthrough". You can find a source code below where I tried to make it as close to the result as possible. How it could be resolved?
struct CustomTextField: View {
let placeholder: String
#Binding var text: String
var body: some View {
TextField("", text: $text)
.placeholder(when: $text.wrappedValue.isEmpty,
alignment: .leading,
placeholder: {
Text(placeholder)
.foregroundColor(.gray)
.font(.system(size: 20))
.padding(.leading, 15)
})
.foregroundColor(.gray)
.font(.system(size: 20))
.padding(EdgeInsets(top: 15, leading: 10, bottom: 15, trailing: 10))
.background {
ZStack {
RoundedRectangle(cornerRadius: 5)
.stroke(.gray, lineWidth: 1)
Text(placeholder)
.foregroundColor(.gray)
.padding(2)
.font(.caption)
.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .topLeading)
.offset(x: 20, y: -10)
}
}
}
}
Here is a solution using .trim on two RoundedRectangles based on the length of the label text, which should give you the result you want:
struct CustomTextField: View {
let placeholder: String
#Binding var text: String
#State private var width = CGFloat.zero
#State private var labelWidth = CGFloat.zero
var body: some View {
TextField(placeholder, text: $text)
.foregroundColor(.gray)
.font(.system(size: 20))
.padding(EdgeInsets(top: 15, leading: 10, bottom: 15, trailing: 10))
.background {
ZStack {
RoundedRectangle(cornerRadius: 5)
.trim(from: 0, to: 0.55)
.stroke(.gray, lineWidth: 1)
RoundedRectangle(cornerRadius: 5)
.trim(from: 0.565 + (0.44 * (labelWidth / width)), to: 1)
.stroke(.gray, lineWidth: 1)
Text(placeholder)
.foregroundColor(.gray)
.overlay( GeometryReader { geo in Color.clear.onAppear { labelWidth = geo.size.width }})
.padding(2)
.font(.caption)
.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .topLeading)
.offset(x: 20, y: -10)
}
}
.overlay( GeometryReader { geo in Color.clear.onAppear { width = geo.size.width }})
.onChange(of: width) { _ in
print("Width: ", width)
}
.onChange(of: labelWidth) { _ in
print("labelWidth: ", labelWidth)
}
}
}
Here is my version of the TextField.
struct TextInputField: View {
let placeHolder: String
#Binding var textValue: String
var body: some View {
ZStack(alignment: .leading) {
Text(placeHolder)
.foregroundColor(Color(.placeholderText))
.offset(y: textValue.isEmpty ? 0 : -25)
.scaleEffect(textValue.isEmpty ? 1: 0.8, anchor: .leading)
TextField("", text: $textValue)
}
.padding(.top, textValue.isEmpty ? 0 : 15)
.frame(height: 52)
.padding(.horizontal, 16)
.overlay(RoundedRectangle(cornerRadius: 12).stroke(lineWidth: 1).foregroundColor(.gray))
.animation(.default)
}
}
The above code is to create a CustomTextField named TextInputField. If you want to use the about component
struct ContentView: View {
#State var itemName: String = ""
var body: some View {
TextInputField(placeHolder: "Item Name": textValue: $itemName)
}
}
I'm using #ChrisR's answer as a base for my answer, so instead of doing all that calculation with two RoundedRectangles and label's width; you can position the Text on top and give it a background matching the app's background color
struct FloatingTitleTextField: View {
let placeholder: String
#Binding var text: String
var body: some View {
TextField("Placeholder", text: $text)
.foregroundColor(.gray)
.font(.system(size: 20))
.padding(EdgeInsets(top: 15, leading: 10, bottom: 15, trailing: 10))
.background {
ZStack {
RoundedRectangle(cornerRadius: 5)
.stroke(.black, lineWidth: 1)
Text(placeholder)
.foregroundColor(.gray)
.padding(2)
.background()
.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .topLeading)
.offset(x: 20, y: -10)
}
}
}
}
When calling the textfield you do it like this
FloatingTitleTextField(placeholder: "Placeholder", text: $text)
I also found this article very helpful

SwiftUI simple view, need a push into right direction

I am complete beginner with SwiftUI and I can't wrap my head around how to connect these images with views that represents lines. Now I simply have 3 VStacks with image and text and put them into a HStack, but don't know how to connect these images with a line shown in red in the picture I attached. Note that there's some space between the line and the image. I need general direction and some hints, full working code not necessary.
Thank you.
How's this?
In SwiftUI, you use HStacks and VStacks to stack your Views. For the red line, a Rectangle should do. Here's the code:
struct ContentView: View {
var body: some View {
HStack { /// horizontal stack
VStack {
Image(systemName: "face.smiling")
.font(.system(size: 80))
.padding()
.border(Color.black, width: 5)
Text("Text TEXTEXT")
}
Rectangle()
.fill(Color.red)
.frame(height: 5)
VStack {
Image(systemName: "face.smiling")
.font(.system(size: 80))
.padding()
.border(Color.black, width: 5)
Text("Text TEXTEXT")
}
Rectangle()
.fill(Color.red)
.frame(height: 5)
VStack {
Image(systemName: "face.smiling")
.font(.system(size: 80))
.padding()
.border(Color.black, width: 5)
Text("Text TEXTEXT")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.fixed(width: 800, height: 200))
}
}
Version 1.0.0
I decided to give my answer which is same like aheze answer with this difference that you can have CustomVerticalAlignment as well! As I see in your Image in question you want that also:
with CustomVerticalAlignment: In center!
without CustomVerticalAlignment: off center!
import SwiftUI
struct ContentView: View {
var body: some View {
HStack(alignment: .customVerticalAlignment) {
VStack {
Image(systemName: "star")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50, alignment: .center)
.padding()
.border(Color.black, width: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
Text("Text")
}
Capsule()
.fill(Color.red)
.frame(height: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
VStack {
Image(systemName: "star")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50, alignment: .center)
.padding()
.border(Color.black, width: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
Text("Text")
}
Capsule()
.fill(Color.red)
.frame(height: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
VStack {
Image(systemName: "star")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50, alignment: .center)
.padding()
.border(Color.black, width: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
Text("Text")
}
}
.padding()
}
}
extension VerticalAlignment {
struct CustomVerticalAlignment: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
d[VerticalAlignment.center]
}
}
static let customVerticalAlignment = VerticalAlignment(CustomVerticalAlignment.self)
}
Update Version 2.0.0
About this version: I would say it does the same job of version 1.0.0 in less code and also Text and Line are not depending on VStack or eachother any moere!
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
image.overlay(text.offset(y: 40), alignment: .bottom)
capsule
image.overlay(text.offset(y: 40), alignment: .bottom)
capsule
image.overlay(text.offset(y: 40), alignment: .bottom)
}
.padding(50)
}
var image: some View {
return Image(systemName: "star.fill")
.resizable()
.scaledToFit()
.padding(10)
.shadow(radius: 10)
.frame(width: 50, height: 50, alignment: .center)
.foregroundColor(Color.red)
.background(Color.yellow)
.border(Color.black, width: 5)
}
var capsule: some View {
return Capsule()
.fill(Color.red)
.frame(height: 5)
}
var text: some View {
return Text("Hello World!")
.lineLimit(1)
.fixedSize()
}
}
You could define a Shape that represents your line.
I used the spacing parameter of HStack to do the spacing:
struct MyLine : Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: 0, y: rect.midY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
}
}
}
struct ContentView: View {
var body: some View {
HStack(spacing: 10) {
VStack {
Image(systemName: "pencil")
Text("Label")
}
MyLine().stroke(Color.red)
VStack {
Image(systemName: "pencil")
Text("Label 2")
}
MyLine().stroke(Color.red)
VStack {
Image(systemName: "pencil")
Text("Label 3")
}
}
}
}
You could add a lineWidth parameter to make the stroke thicker:
.stroke(Color.red, lineWidth: 4)
Also, if you didn't using spacing on the HStack, you could using a padding modifier on either the VStacks or the MyLines to get the spacing.

Add new item in Scroll View in SwiftUI

I'm trying to add a new view inside the Scroll View that contains a button every time that I click in the blue button in the bottom
]1
Here i create the scroll view with 2 buttons, and want to add more after I click in the button on the right
HStack{
ScrollView(.horizontal, content: {
HStack{
PageStep()
PageStep()
}
})
Button(action: {
self.addNewStep = true
}) {
Image(systemName: "plus.square")
.frame(width: 75, height: 75)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.blue, lineWidth: 5)
)
}.buttonStyle(PlainButtonStyle()).padding(.trailing, 10)
}
.padding(.leading, 10)
.frame(minWidth: 0, maxWidth: UIScreen.main.bounds.width, minHeight: 80, alignment: .bottom)
.foregroundColor(Color.blue)
struct PageStep: View {
var stepPossition = String()
var body: some View {
Button(action: {
print("Entrou")
}){
Rectangle()
.fill(Color.red)
.frame(width: 75, height: 75)
.cornerRadius(10)
}
}
}
Here is possible approach. Tested with Xcode 11.4.
struct ContentView: View {
#State private var steps = 2 // pages counter
var body: some View {
HStack{
ScrollView(.horizontal, content: {
HStack{
// create available pages
ForEach(0..<steps, id: \.self) { i in
PageStep(stepPossition: "\(i)").id(i) // inject
}
}
})
Button(action: {
self.steps += 1 // << add next page
}) {
Image(systemName: "plus.square")
.frame(width: 75, height: 75)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.blue, lineWidth: 5)
)
}.buttonStyle(PlainButtonStyle()).padding(.trailing, 10)
}
.padding(.leading, 10)
.frame(minWidth: 0, maxWidth: UIScreen.main.bounds.width, minHeight: 80, alignment: .bottom)
.foregroundColor(Color.blue)
}
}
struct PageStep: View {
var stepPossition: String
var body: some View {
Button(action: {
print("Entrou")
}){
Rectangle()
.fill(Color.red)
.frame(width: 75, height: 75)
.cornerRadius(10)
}
}
}

SwiftUI won't align ZStacks

I'm currently trying to handle SwiftUI by following a tutorial, but somehow I can't solve one issue:
I created another View, namely my HomeView.swift - this file contains the following code:
import SwiftUI
struct Home: View {
var menu = menuData
#State var show = false
var body: some View {
ZStack {
ZStack(alignment: .topLeading) {
Button(action: { self.show.toggle() }) {
HStack {
Spacer()
Image(systemName: "list.dash")
.foregroundColor(Color("primary"))
}
.padding(.trailing, 20)
.frame(width: 90, height: 60)
.background(Color.white)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
Spacer()
}
ZStack(alignment: .topTrailing) {
Button(action: { self.show.toggle() }) {
HStack {
Spacer()
Image(systemName: "map.fill")
.foregroundColor(Color("primary"))
}
.padding(.trailing, 20)
.frame(width: 44, height: 44)
.background(Color.white)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
Spacer()
}
MenuView(show: $show)
}
}
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
Home()
}
}
struct MenuRow: View {
var text: String?
var image: String?
var body: some View {
HStack {
Image(systemName: image ?? "")
.foregroundColor(Color("third"))
.frame(width: 32, height: 32, alignment: .trailing)
Text(text ?? "")
.font(Font.custom("Helvetica Now Display Bold", size: 15))
.foregroundColor(Color("primary"))
Spacer()
}
}
}
struct Menu: Identifiable {
var id = UUID()
var title: String
var icon: String
}
let menuData = [
Menu(title: "My Account", icon: "person.crop.circle.fill"),
Menu(title: "Reservations", icon: "house.fill"),
Menu(title: "Sign Out", icon: "arrow.uturn.down")
]
struct MenuView: View {
var menu = menuData
#Binding var show: Bool
var body: some View {
VStack(alignment: .leading, spacing: 20) {
ForEach(menu) { item in
MenuRow(text: item.title, image: item.icon)
}
Spacer()
}
.padding(.top, 20)
.padding(30)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.white)
.cornerRadius(30)
.padding(.trailing, 60)
.shadow(radius: 20)
.rotation3DEffect(Angle(degrees: show ? 0 : 60), axis: (x: 0, y: 10, z: 0))
.animation(.default)
.offset(x: show ? 0 : -UIScreen.main.bounds.width)
.onTapGesture {
self.show.toggle()
}
}
}
As you can see, right in the beginning, inside of my Home struct, I tried to align two ZStacks - one .topLeading and one .topTrailing. Reading the docs, this should change its position, but somehow it doesn't. Both stack stay centered.
BTW I haven't particularly touched ContenView.swift yet.
Actually, for either inner ZStack, you need to set frames. This can make them reach edges.
ZStack{
ZStack{
Button(action: { self.show.toggle() }) {
HStack {
Spacer()
Image(systemName: "list.dash")
.foregroundColor(Color("primary"))
}
.padding(.trailing, 20)
.frame(width: 90, height: 60)
.background(Color.white)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
Spacer()
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
ZStack{
Button(action: { self.show.toggle() }) {
HStack {
Spacer()
Image(systemName: "map.fill")
.foregroundColor(Color("primary"))
}
.padding(.trailing, 20)
.frame(width: 44, height: 44)
.background(Color.white)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
Spacer()
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
MenuView(show: $show)
}
struct Home: View {
var menu = menuData
#State var show = false
var body: some View {
ZStack {
VStack {
HStack {
Button(action: { self.show.toggle() }) {
HStack {
Spacer()
Image(systemName: "list.dash")
.foregroundColor(Color("primary"))
}
.padding(.trailing, 20)
.frame(width: 90, height: 60)
.background(Color.white)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
Spacer()
Button(action: { self.show.toggle() }) {
HStack {
Spacer()
Image(systemName: "map.fill")
.foregroundColor(Color("primary"))
}
.padding(.trailing, 20)
.frame(width: 44, height: 44)
.background(Color.white)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
}
Spacer()
}
MenuView(show: $show)
}
}
}
Is this the layout that you are looking for? With VStack and HStack you can align the views to the top and on both edges

ScrollView content not filling in SwiftUI

I have a scrollview which content is a VStack containing a ForEach loop to create some Rows instead of a list. A List has some downsides like the dividers.
My Issue is that the Row is not filling the scrollview. I think the scrollview width is not filling the screen.
NavigationView {
Toggle(isOn: $onlineStatus) {
Text("Online Only")
}.padding([.leading, .trailing], 15)
ScrollView {
VStack(alignment: .trailing) {
ForEach(onlineStatus ? self.notes.filter { $0.dot == .green } : self.notes) { note in
NavigationButton(destination: Text("LOL")) {
CardRow(note: note)
.foregroundColor(.primary)
.cornerRadius(8)
.shadow(color: .gray, radius: 3, x: 0, y: -0.01)
}.padding([.leading, .trailing, .top], 5)
}.animation(self.onlineStatus ? .fluidSpring() : nil)
}
}.padding([.leading, .trailing])
.navigationBarTitle(Text("Your documents"))
}
This is giving me this result:
That's my CardRow:
struct CardRow: View {
var note: Note
var body: some View {
HStack {
Image(uiImage: UIImage(named: "writing.png")!)
.padding(.leading, 10)
VStack(alignment: .leading) {
Group {
Text(note.message)
.font(.headline)
Text(note.date)
.font(.subheadline)
}
.foregroundColor(.black)
}
Spacer()
VStack(alignment: .trailing) {
Circle().foregroundColor(note.dot)
.frame(width: 7, height: 7)
.shadow(radius: 1)
.padding(.trailing, 5)
Spacer()
}.padding(.top, 5)
}
.frame(height: 60)
.background(Color(red: 237/255, green: 239/255, blue: 241/255))
}
}
Use .frame(minWidth: 0, maxWidth: .infinity) on the RowView or inside of it
Best solution I found is to use GeometryReader to fill the ScrollView's contents to the outer view's width. And be sure to use a Spacer() in each row's HStack. This handles safe areas and rotation well.
struct Card : View {
var body: some View {
HStack {
VStack(alignment: .leading) {
Text("Header")
.font(.headline)
Text("Description")
.font(.body)
}
Spacer()
}
.padding()
.background(Color.white)
.cornerRadius(8)
.shadow(color: Color.black.opacity(0.15), radius: 8, x: 0, y: 0)
}
}
struct Home : View {
var body: some View {
GeometryReader { geometry in
NavigationView {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Card()
Card()
Card()
}
.padding()
.frame(width: geometry.size.width)
}
.navigationBarTitle(Text("Home"))
}
}
}
}
See resulting screenshot:
Try setting the frame width of the RowView to UIScreen.main.bounds.width:
.frame(width: UIScreen.main.bounds.width)

Resources