Why does the interface twitch when changing the state variable? - ios

Content view:
#State var login: String = ""
#State var password: String = ""
#State var isLoginTextViewActive = false
#State var isPasswordTextViewActive = false
VStack {
TextField("Login", text: $login, onEditingChanged: { isEditing in
print(isEditing)
self.isLoginTextViewActive = isEditing
}
)
TextField("Password", text: $password)
HorizontalLine(color: isLoginTextViewActive ? Color.yellow : Color.black, height: CGFloat(1))
}.frame(width: CGFloat(200), alignment: .center)
Line code:
struct HorizontalLineShape: Shape {
func path(in rect: CGRect) -> Path {
let fill = CGRect(x: 0, y: 0, width: rect.size.width, height: rect.size.height)
var path = Path()
path.addRoundedRect(in: fill, cornerSize: CGSize(width: 2, height: 2))
return path
}
}
struct HorizontalLine: View {
private var color: Color? = nil
private var height: CGFloat = 1.0
init(color: Color, height: CGFloat = 1.0) {
self.color = color
self.height = height
}
var body: some View {
HorizontalLineShape().fill(self.color!).frame(minWidth: 0, maxWidth: .infinity, minHeight: height, maxHeight: height)
}
}
When you change the focus between "TextView", they begin to twitch as if due to a change in height. I checked, every time the line initializer is called, but what to do with it I do not know.

This happens because there is a bug with the font size in textfields currently. If you set your font size manually for example 20, it will work like you want.
var body: some View {
VStack {
TextField("Login", text: $login, onEditingChanged: { isEditing in
print(isEditing)
self.isLoginTextViewActive = isEditing
})
.font(.system(size: 20, weight: .medium, design: .default)) // SET SIZE
TextField("Password", text: $password)
.font(.system(size: 20, weight: .medium, design: .default)) // SET SIZE
Rectangle()
.frame(height: 1)
.foregroundColor(isLoginTextViewActive ? Color.yellow : Color.black)
}.frame(width: CGFloat(200), alignment: .center)
}

Related

How to design floating header in securedField the text set as header when enter in the SecuredField in Swiftui?

I want to set the text as header when the field enter But it does not
work as I want. This is my code section It works but whenever I select
the texts and then write again it shows clear foreground text.
struct PasswordField: View{
#State var isTapped = true
let textFieldHeight: CGFloat = 50
private var placeHolderText: String = ""
#State var leadingImage: String = "lock.fill"
#Binding var text: String
#State private var isEditing = false
public init(placeHolder: String,
text: Binding<String>) {
self._text = text
self.placeHolderText = placeHolder
self.leadingImage = leadingImage
}
var shouldPlaceHolderMove: Bool {
isEditing || (text.count != 0)
}
var body: some View {
ZStack(alignment: .leading) {
ZStack {
if isTapped{
// SecureField("",text: $text)
ZStack {
SecureField("", text: $text)
.font(.system(size: 22))
TextField("", text: $text, onEditingChanged: { (edit) in
isEditing = edit
})
.foregroundColor(.clear)
.disableAutocorrection(true)
.font(.system(size: 22))
}
.padding(.leading,40)
.padding(.trailing,50)
.foregroundColor(Color.white)
.accentColor(Color.teal)
.font(.system(size: 22))
.animation(.linear, value: true)
} else{
TextField("", text: $text, onEditingChanged: { (edit) in
isEditing = edit
})
.padding(.leading,40)
.padding(.trailing,50)
.foregroundColor(Color.white)
.accentColor(Color.teal)
.font(.system(size: 22))
.animation(.linear, value: true)
}
}.overlay(alignment: .trailing){
Image(systemName: isTapped ? "eye.slash.fill": "eye.fill").font(.system(size: 28))
.foregroundColor(.gray).padding(4).onTapGesture{
isTapped.toggle()
}
}
//Floating Placeholder
Text(placeHolderText)
.foregroundColor(.white)
.padding(shouldPlaceHolderMove ?
EdgeInsets(top: 2, leading:40, bottom: textFieldHeight, trailing: 0) :
EdgeInsets(top: 0, leading:50, bottom: 0, trailing: 0))
.scaleEffect(shouldPlaceHolderMove ? 1.2 : 1.3)
.animation(.easeInOut, value: 91)
.overlay(alignment: .leading){
Image(systemName: leadingImage)
.foregroundColor(Color.gray)
.font(.system(size: 28)).padding(4)
}
}
.underlineTextField()
.foregroundColor(shouldPlaceHolderMove ? Color.teal: Color.white)
.frame( height: 90)
}
}
it does not work properly. let me know where I am getting wrong? in my
code Textfield used for isEditting attribute Use only. is there any
other way for getting this functionality in the SecuredField.

How would I set each instance of a class to a specific page for a TabView

I am trying to assign each weather location in my app a page in a tab view but am unsure on how to achieve this.
I have a currentPage variable in my location view which should add 1 every time a weather location is shown on screen but instead it just keeps adding one throughout the entire foreach loop I have, so a page number isn't actually assigned to a weather view.
I was wondering if anyone knew how I could assign a page number to each location when iterating through my for each loop.
My location view is shown below. The code I tried is at the bottom in the onAppear modifier in the if statement but it doesn't work like I said above and I am not sure if this is even the right approach for doing something like this. Any help would be great!
struct LocationView: View {
#ObservedObject var weatherVM: WeatherViewViewModel
#ObservedObject var multiLocationVM: MultiLocationViewModel
#State private var pageSelection = 1
#State var currentPage: Int = 0
var body: some View {
ZStack {
LinearGradient(colors: [Color("BackgroundColorSun"), Color("BackgroundColorSky")], startPoint: .topLeading, endPoint: UnitPoint(x: 0.5, y: 0.5)).ignoresSafeArea()
if multiLocationVM.weatherVM.count == 0 {
BlankLocationView(multiLocationVM: multiLocationVM)
} else {
TabView(selection: $pageSelection) {
ForEach(multiLocationVM.weatherVM, id: \.self.city) { location in
VStack(spacing: 0) {
VStack(spacing: -10) {
ZStack(alignment: .center) {
Text("\(location.getWeatherIconFor(icon: location.weatherIcon))")
//.innerShadow(Color(.red))
.font(.custom("SF Pro Text", size: 64))
.innerShadow()
//.foregroundColor(.gray)
.offset(x: -70, y: -35)
Text("\(location.getTempFor(temp: location.weather.current.temp))°")
.font(.system(size: 96, weight: .semibold, design: .rounded))
.tracking(-0.41)
.shadow(color: .black.opacity(0.25), radius: 4, x: 0, y: 4)
}
.offset(x: 15)
Text(location.conditions)
.font(.custom("SF Pro Display", size: 24))
}
.padding(EdgeInsets(top: -8, leading: 0, bottom: -8, trailing: 0))
VStack(spacing: -20) {
HourlyWeatherView(weatherVM: location)
DailyWeatherView(weatherVM: location)
}
Spacer()
}
.onAppear() {
if location.city != weatherVM.city {
weatherVM.city = location.city
currentPage += multiLocationVM.weatherVM.firstIndex(of: location)!
location.page = currentPage
print("current \(currentPage)")
print("Location \(location.page)")
} else {
return
}
}
.tag(currentPage)
}
}
.tabViewStyle(.page(indexDisplayMode: .automatic))
.indexViewStyle(.page(backgroundDisplayMode: .interactive))
}
}
.padding(.bottom, 70)
.ignoresSafeArea()
}
}
struct LocationView_Previews: PreviewProvider {
static var previews: some View {
LocationView(weatherVM: WeatherViewViewModel(city: "Phoenix"), multiLocationVM: MultiLocationViewModel())
}
}
IMHO you don't need the currentPage counter as TabView(selection: )is handling that for you. The selection, in your case pageSelection will update when the user swipes, or you can set it programmatically. You just have to provide a fitting .tag:
// dummy test data
struct Weather {
var city: String
var icon: String
var temp: Int
var condition: String
}
let multiLocationVM = [
Weather(city: "Vancouver", icon: "cloud", temp: 64, condition: " Mostly Cloudy"),
Weather(city: "New York", icon: "cloud.sun", temp: 78, condition: " Mostly Sunny"),
Weather(city: "Los Angeles", icon: "sun.max", temp: 91, condition: " Sunny"),
]
struct ContentView: View {
#State private var pageSelection = "Vancouver" // default here
#State var currentPage: Int = 0
var body: some View {
ZStack {
LinearGradient(colors: [Color(.yellow), Color(.blue)], startPoint: .topLeading, endPoint: UnitPoint(x: 0.5, y: 0.5)).ignoresSafeArea()
if multiLocationVM.count == 0 {
// BlankLocationView(multiLocationVM: multiLocationVM)
} else {
TabView(selection: $pageSelection) {
ForEach(multiLocationVM, id: \.city) { location in
LocalWeather(location: location)
.tag(location.city) // set tab id here
}
}
.tabViewStyle(.page(indexDisplayMode: .automatic))
}
}
.padding(.bottom, 70)
.ignoresSafeArea()
}
}
struct LocalWeather: View {
let location: Weather
var body: some View {
VStack(alignment: .center, spacing: 0) {
VStack(alignment: .center, spacing: -10) {
ZStack(alignment: .center) {
Image(systemName: location.icon)
.font(.custom("SF Pro Text", size: 32))
.offset(x: -90, y: -35)
Text("\(location.temp)")
.font(.system(size: 96, weight: .semibold, design: .rounded))
.tracking(-0.41)
.shadow(color: .black.opacity(0.25), radius: 4, x: 0, y: 4)
}
.offset(x: 15)
Text(location.condition)
.font(.custom("SF Pro Display", size: 24))
}
.padding(EdgeInsets(top: -8, leading: 0, bottom: -8, trailing: 0))
Spacer()
}
}
}

Animate ViewBuilder content size change in SwiftUI

I am wondering how I can animate the content size of a ViewBuilder view. I have this:
struct CardView<Content>: View where Content: View {
private let content: Content
init(#ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
VStack(spacing: 0) {
content
.padding(16)
}
.background(.white)
.cornerRadius(14)
.shadow(color: .black.opacity(0.07), radius: 12, x: 0, y: 2)
}
}
I would like to animate any size changes to content, but I can't find a nice way of doing this. I found two ways that work:
Using animation(.linear) in CardView works, but is deprecated and discouraged since I have no value to attach the animation to.
Using withAnimation inside content when changing the content works, too, but I would like to encapsulate this behaviour in CardView. CardView is heavily reused and doing it in content is easy to forget and also not where this behaviour belongs in my opinion.
I also tried using GeometryReader but could not find a good way of doing it.
Here is an approach for you:
You may take look at this link as well:
How to replace deprecated .animation() in SwiftUI?
struct ContentView: View {
#State private var cardSize: CGSize = CGSize(width: 150, height: 200)
var body: some View {
VStack {
CardView(content: {
Color.red
.overlay(Image(systemName: "dollarsign.circle").resizable().scaledToFit().padding())
.onTapGesture {
cardSize = CGSize(width: cardSize.width + 50, height: cardSize.height + 50)
}
}, cardSize: cardSize)
}
}
}
struct CardView<Content>: View where Content: View {
let content: Content
let cardSize: CGSize
init(#ViewBuilder content: () -> Content, cardSize: CGSize) {
self.content = content()
self.cardSize = cardSize
}
var body: some View {
content
.frame(width: cardSize.width, height: cardSize.height)
.cornerRadius(14)
.padding(16)
.background(.white)
.shadow(color: .black.opacity(0.07), radius: 12, x: 0, y: 2)
.animation(.easeInOut, value: cardSize)
}
}
You might find this useful.
It uses a looping animation and a user gesture for add size and resting.
struct PilotTestPage: View {
#State private var cardSize = CGSize(width: 150, height: 200)
var body: some View {
return ZStack {
Color.yellow
CardView() {
Color.clear
.overlay(
Image(systemName: "dollarsign.circle")
.resizable()
.scaledToFit()
.padding()
)
}
.frame(
width: cardSize.width
,height: cardSize.height
)
.onTapGesture {
withAnimation {
cardSize = CGSize(
width: cardSize.width + 50
,height: cardSize.height + 50
)
}
}
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(.red)
.frame(
width: 200
,height: 44
)
.offset(y: -300)
.onTapGesture {
withAnimation {
cardSize = CGSize(
width: 150
,height: 200
)
}
}
}
.ignoresSafeArea()
}
struct CardView<Content>: View where Content: View {
let content: Content
init(
#ViewBuilder content: () -> Content
) {
self.content = content()
}
#State private var isAtStart = true
var body: some View {
ZStack {
content
.background(
RoundedRectangle(cornerRadius: 12)
.fill(.white)
.shadow(
color: .black.opacity(0.25)
,radius: 12
,x: 0
,y: 2
)
)
}
.scaleEffect(isAtStart ? 0.9 : 1.0)
.rotationEffect(.degrees(isAtStart ? -2 : 2))
.onAppear {
withAnimation(
.easeInOut(duration: 1)
.repeatForever()
) {
self.isAtStart.toggle()
}
}
}
}
}

Fill remaining whitespace in text line with dots (multiline text) iOS

I want to fill the remaining whitespace from the last line with a dotted line. It should start at the end of the last word and continue until the end of the line. Is this possible with SwiftUI or even UIKit?
What I have:
What I need:
struct ContentView: View {
var body: some View {
let fontSize = UIFont.preferredFont(forTextStyle: .headline).lineHeight
let text = "stackoverflow stackoverflow stackoverflow stackoverflow stackoverflow stackoverflow stackoverflow stackoverflow"
HStack(alignment: .lastTextBaseline, spacing: .zero) {
HStack(alignment: .top, spacing: .zero) {
Circle()
.foregroundColor(.green)
.frame(width: 6, height: 6)
.frame(height: fontSize, alignment: .center)
ZStack(alignment: .bottom) {
HStack(alignment: .lastTextBaseline, spacing: .zero) {
Text("")
.font(.headline)
.padding(.leading, 5)
Spacer(minLength: 10)
.overlay(Line(), alignment: .bottom)
}
HStack(alignment: .lastTextBaseline, spacing: .zero) {
Text(text)
.font(.headline)
.padding(.leading, 5)
Spacer(minLength: 10)
}
}
}
}
}
}
struct Line: View {
var width: CGFloat = 1
var color = Color.gray
var body: some View {
LineShape(width: width)
.stroke(style: StrokeStyle(lineWidth: 3, dash: [3]))
.foregroundColor(color)
.frame(height: width)
}
}
private struct LineShape: Shape {
var width: CGFloat
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint())
path.addLine(to: CGPoint(x: rect.width, y: .zero))
return path
}
}
Here's my slightly hacky, but more simple, solution: add a white highlight to the text, so it covers the dotted line.
We can add a highlight with NSAttributedString. SwiftUI doesn't support this by default, so we need to use UIViewRepresentable. Here it is, based off this answer:
struct HighlightedText: View {
var text: String
#State private var height: CGFloat = .zero
private var fontStyle: UIFont.TextStyle = .body
init(_ text: String) { self.text = text }
var body: some View {
InternalHighlightedText(text: text, dynamicHeight: $height, fontStyle: fontStyle)
.frame(minHeight: height) /// allow text wrapping
.fixedSize(horizontal: false, vertical: true) /// preserve the Text sizing
}
struct InternalHighlightedText: UIViewRepresentable {
var text: String
#Binding var dynamicHeight: CGFloat
var fontStyle: UIFont.TextStyle
func makeUIView(context: Context) -> UILabel {
let label = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
label.font = UIFont.preferredFont(forTextStyle: fontStyle)
return label
}
func updateUIView(_ uiView: UILabel, context: Context) {
let attributedText = NSAttributedString(string: text, attributes: [.backgroundColor: UIColor.systemBackground])
uiView.attributedText = attributedText /// set white background color here
uiView.font = UIFont.preferredFont(forTextStyle: fontStyle)
DispatchQueue.main.async {
dynamicHeight = uiView.sizeThatFits(CGSize(width: uiView.bounds.width, height: CGFloat.greatestFiniteMagnitude)).height
}
}
}
/// enable .font modifier
func font(_ fontStyle: UIFont.TextStyle) -> HighlightedText {
var view = self
view.fontStyle = fontStyle
return view
}
}
Then, just replace Text(text) with HighlightedText(text).
struct ContentView: View {
var body: some View {
let fontSize = UIFont.preferredFont(forTextStyle: .headline).lineHeight
let text = "stackoverflow stackoverflow stackoverflow stackoverflow stackoverflow stackoverflow stackoverflow stackoverflow"
HStack(alignment: .lastTextBaseline, spacing: .zero) {
HStack(alignment: .top, spacing: .zero) {
Circle()
.foregroundColor(.green)
.frame(width: 6, height: 6)
.frame(height: fontSize, alignment: .center)
ZStack(alignment: .bottom) {
HStack(alignment: .lastTextBaseline, spacing: .zero) {
Text("")
.font(.headline)
.padding(.leading, 5)
Spacer(minLength: 10)
.overlay(Line(), alignment: .bottom)
}
HStack(alignment: .lastTextBaseline, spacing: .zero) {
HighlightedText(text) /// here!
.font(.headline)
.padding(.leading, 5)
Spacer(minLength: 10)
}
}
}
}
}
}
struct Line: View {
var width: CGFloat = 1
var color = Color.gray
var body: some View {
LineShape(width: width)
.stroke(style: StrokeStyle(lineWidth: 3, dash: [3]))
.foregroundColor(color)
.frame(height: width)
}
}
private struct LineShape: Shape {
var width: CGFloat
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint())
path.addLine(to: CGPoint(x: rect.width, y: .zero))
return path
}
}
Before
After
You can do that with UIKit:
Find the text in the last line using this approach: https://stackoverflow.com/a/14413484/2051369
Get the size of last line using let size = lastLineText.size(withAttributes: [.font: label.font]) ?? .zero
let gap = label.frame.width - size.width
let dotsCount = gap / dotWidth
let resultText = sourceText + String(repeating: " .", count: dotsCount)

SwiftUI: Padding inside TextField

I have a TextField in SwiftUI. When I apply padding to it as
TextField(text, text: $value)
.padding()
the padding is outside of the tap area of the TextField, i.e. tapping on the padding does not bring the text field into focus.
I would like to be able to focus the TextField even if the user taps on the padding.
You can try this. At least worked for me. Hope that helps someone:
TextField("", text: $textfieldValueBinding)
.frame(height: 48)
.padding(EdgeInsets(top: 0, leading: 6, bottom: 0, trailing: 6))
.cornerRadius(5)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(lineWidth: 1.0)
)
you can check this. for now i only did with top and bottom padding. you can do the same with the leading and trailing(or with horizontal and vertical).
as asked in the question this would do this
"I would like to be able to focus the TextField even if the user taps on the padding."
struct ContentView: View {
#State var name = ""
#State var isTextFieldFocused = false
var body: some View {
ZStack {
HStack{
Text(name)
.font(.system(size: 50 , weight: .black))
.foregroundColor(isTextFieldFocused ? Color.clear : Color.black)
Spacer()
}
TextField(name, text: $name , onEditingChanged: { editingChanged in
isTextFieldFocused = editingChanged
})
.font(.system(size: isTextFieldFocused ? 50 : 100 , weight: .black))
.foregroundColor(isTextFieldFocused ? Color.black : Color.clear)
.frame(width: 300, height: isTextFieldFocused ? 50 : 100 , alignment: .center)
.padding(.leading, isTextFieldFocused ? 25 : 0 )
.padding(.trailing, isTextFieldFocused ? 25 : 0 )
.padding(.top,isTextFieldFocused ? 25 : 0 )
.padding(.bottom,isTextFieldFocused ? 25 : 0 )
}.frame(width: 300)
.background(Color.red.opacity(0.2))
}
}
Yes, but you have to create your own TextFieldStyle. Here's an example:
struct CustomTextField: View {
public struct CustomTextFieldStyle : TextFieldStyle {
public func _body(configuration: TextField<Self._Label>) -> some View {
configuration
.font(.largeTitle) // set the inner Text Field Font
.padding(10) // Set the inner Text Field Padding
//Give it some style
.background(
RoundedRectangle(cornerRadius: 5)
.strokeBorder(Color.primary.opacity(0.5), lineWidth: 3))
}
}
#State private var password = ""
#State private var username = ""
var body: some View {
VStack {
TextField("Test", text: $username)
.textFieldStyle(CustomTextFieldStyle()) // call the CustomTextField
SecureField("Password", text: $password)
.textFieldStyle(CustomTextFieldStyle())
}.padding()
}
}
For iOS 15 and above you can use this:
var body: some View {
TextField("placeholder", text: $text).focusablePadding()
}
extension View {
func focusablePadding(_ edges: Edge.Set = .all, _ size: CGFloat? = nil) -> some View {
modifier(FocusablePadding(edges, size))
}
}
private struct FocusablePadding : ViewModifier {
private let edges: Edge.Set
private let size: CGFloat?
#FocusState private var focused: Bool
init(_ edges: Edge.Set, _ size: CGFloat?) {
self.edges = edges
self.size = size
self.focused = false
}
func body(content: Content) -> some View {
content
.focused($focused)
.padding(edges, size)
.contentShape(Rectangle())
.onTapGesture { focused = true }
}
}
TextField(
"TextFiled",
text: $teamNames,
prompt: Text("Text Field")
.foregroundColor(.gray)
)
.padding(5)
.frame(width: 250, height: 50, alignment: .center)
.background(
RoundedRectangle(cornerRadius: 25, style: .continuous)
.foregroundColor(.white)
.padding(.horizontal, -30)
)
I don't know if you can give padding inside of a TextField, but you can pad it from outside environment and give it a background of a shape with the same color of your TextField background.
Try this;
TextField("Username", text: $value)
.background(Color.yellow)
.padding(50)
.background(Capsule().fill(Color.white))

Resources