I am trying to animate text to make it scroll across the screen, (using it to make a stock app), I am unable to get it to go completely off the screen can someone please help...
This is what I have so far
let text = "Some text to animate"
private var is = true
var body: some View {
VStack {
Text(text)
.fixedSize()
.frame(width: 100, alignment: is ? .trailing : .leading)
.animation(Animation.linear(duration: 5).repeatForever())
}
A possible solution is to use single Text with .move asymmetric transition.
Here is a simplified demo. Tested with Xcode 13.4 / iOS 15.5
Main part:
var body: some View {
GeometryReader { gp in
VStack {
Text(text)
.fixedSize()
.frame(width: gp.size.width + textWidth, alignment: .trailing)
.id(go)
.transition(transition)
.onAppear{ go.toggle() }
.animation(animation, value: go)
}
}.fixedSize(horizontal: false, vertical: true)
}
Test module on GitHub
like this? It shifts the text using .offset if go is true.
let text = "Some text to animate"
#State private var go = false
var body: some View {
VStack {
Text(text)
.fixedSize()
.frame(width: 100)
.offset(x: go ? 300 : 0, y: 0)
.animation(Animation.linear(duration: 3).repeatForever(), value: go)
.onAppear{self.go.toggle()}
}
}
Related
I am trying to make a SwiftUI TextEditor with a Divider that adapts its position to stay under the bottom-most line of text inside of a edit-bio section of the app.
Note: I have a frame on my TextEditor so that it doesn't take up the whole-screen
Right now the Divider is static and stays in one place. Is there a built-in way to make the divider stay under the bottom most line of text?
I would think the Spacer would have given me this behavior?
Thank you!
struct EditBio: View {
#ObservedObject var editProfileVM: EditProfileViewModel
var body: some View {
VStack(spacing: 10) {
TextEditor(text: $editProfileVM.bio)
.foregroundColor(.white)
.padding(.top, 70)
.padding([.leading, .trailing], 50)
.frame(minWidth: 100, idealWidth: 200, maxWidth: 400, maxHeight: 200, alignment: .center)
Divider().frame(height: 1).background(.white)
Spacer()
}
}
}
It is doing exactly what you told it to do. But a background color on your TextEditor. You will see that it has a height of 200 + a spacing of 10 from the VStack.
I changed your code to make it obvious:
struct EditBio: View {
#State var editProfileVM = ""
var body: some View {
VStack(spacing: 10) {
TextEditor(text: $editProfileVM)
.foregroundColor(.white)
.padding(.top, 70)
.padding([.leading, .trailing], 50)
.frame(minWidth: 100, idealWidth: 200, maxWidth: 400, maxHeight: 200, alignment: .center)
.background(Color.gray)
Divider().frame(height: 1).background(.red)
Spacer()
}
}
}
to produce this:
You can see the TextEditor naturally wants to be taller than 200, but that is limiting it. Therefore, the Spacer() is not going to cause the TextEditor to be any smaller.
The other problem that setting a fixed frame causes will be that your text will end up off screen at some point. I am presuming what you really want is a self sizing TextEditor that is no larger than it's contents.
That can be simply done with the following code:
struct EditBio: View {
#State var editProfileVM = ""
var body: some View {
VStack(spacing: 10) {
SelfSizingTextEditor(text: $editProfileVM)
// Frame removed for the image below.
// .frame(minWidth: 100, idealWidth: 200, maxWidth: 400, maxHeight: 200, alignment: .center)
.foregroundColor(.white)
// made the .top padding to be .vertical
.padding(.vertical, 70)
.padding([.leading, .trailing], 50)
.background(Color.gray)
Divider().frame(height: 5).background(.red)
Spacer()
}
}
}
struct SelfSizingTextEditor: View {
#Binding var text: String
#State var textEditorSize = CGSize.zero
var body: some View {
ZStack {
Text(text)
.foregroundColor(.clear)
.copySize(to: $textEditorSize)
TextEditor(text: $text)
.frame(height: textEditorSize.height)
}
}
}
extension View {
func readSize(onChange: #escaping (CGSize) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
func copySize(to binding: Binding<CGSize>) -> some View {
self.readSize { size in
binding.wrappedValue = size
}
}
}
producing this view:
I have three views inside an HStack. The first two are HStack's and the third is text. I want to use .offset to make the Text go off screen. I then have a DragGesture() which allows me to pull the view over.
Although when I pull, the text is not there. I played around with the .offset value and when I lowered it so only part of the text is off screen, the rest of the text is not there.
How can I get the text to render/ actually be viewable once I drag the screen?
import SwiftUI
struct ContentView: View {
#State private var draggedOffset = CGSize.zero
var body: some View {
HStack {
Rectangle()
.frame(width: 100, height: 100, alignment: .center)
Spacer()
Rectangle()
.frame(width: 100, height: 100, alignment: .center)
Spacer()
Text("Hello, world! Testing, Testing")
.padding()
.lineLimit(1)
}
.padding()
.animation(.spring())
.offset(x: draggedOffset.width)
.gesture(DragGesture()
.onChanged { value in
self.draggedOffset = value.translation
}
.onEnded { value in
self.draggedOffset = CGSize.zero
}
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
It's a bit tricky. The main thing here is .fixedSize(horizontal: true, vertical: false) - that lets the Text expand horizontally. There's no need for .lineLimit(1).
You should also remove the Spacer()s. Because those also expand horizontally, SwiftUI will get stuck trying to figure out which ones to expand and which to not.
Then, there's the Color.clear. This has a couple purposes:
Give expected resizing behavior: Color can be freely resized, so if you ever want to use ContentView inside another View, you won't run into trouble.
Align the HStack: Because it's wider than the screen width, SwiftUI will center it by default. You want it aligned left, so you can pass in .leading to the alignment parameter.
struct ContentView: View {
#State private var draggedOffset = CGSize.zero
var body: some View {
Color.clear /// placeholder view that fills the screen width
.background( /// use this to constrain everything to the bounds of the screen
HStack {
Rectangle()
.frame(width: 100, height: 100) /// `alignment` is unnecessary here
Rectangle()
.frame(width: 100, height: 100)
Text("Hello, world! Testing, Testing")
.padding()
.fixedSize(horizontal: true, vertical: false) /// make the Text extend as much as possible horizontally
}, alignment: .leading /// align everything left
)
.padding()
.animation(.spring())
.offset(x: draggedOffset.width)
.gesture(
DragGesture()
.onChanged { value in
self.draggedOffset = value.translation
}
.onEnded { value in
self.draggedOffset = CGSize.zero
}
)
}
}
Result:
My first ever question here at Stack Overflow.
I'm writing a small application for iOS and macOS. Text entry is done via (now) a TextField and a Button. The issue with the TextField is that it's a single line and doesn't allow for multiline text entry. So, I tried using TextEditor instead, but I can either set it up to not grow as more text is added, or it shows up very big to begin with.
What I'm saying is that ideally, it would mimic the behavior that the text entry in iMessage has: starts as the same size of a TextField but grows if needed to accommodate a multiline text like a TextEditor.
Here's the code I am using for this view:
var inputView: some View {
HStack {
ZStack {
//tried this here...
//TextEditor(text: $taskText)
TextField("New entry...", text: $taskText, onCommit: { didTapAddTask() })
.frame(maxHeight: 35)
.padding(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 0))
.clipped()
.accentColor(.black)
.cornerRadius(8)
.textFieldStyle(RoundedBorderTextFieldStyle())
Text(taskText).opacity(0).padding(.all, 8)
}
Button(action: AddNewEntry, label: { Image(systemName: "plus.circle")
.imageScale(.large)
.foregroundColor(.primary)
.font(.title) }).padding(15).foregroundColor(.primary)
}
}
Any way of doing this?
I tried different approaches found in different questions from other users, but I can't quite figure out.
Here's how it looks:
How the TextEdit looks
I also have tried something like this and played with different values for the .frame:
TextEditor(text: $taskText)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200)
.border(Color.primary, width: 1)
.padding(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 0))
Any help is appreciated.
Thanks.
Oh, and I'm using Xcode 12.5.1 and target is iOS 14.x and macOS Big Sur for now.
EDIT to answer jnpdx.
When I add the code from Dynamic TextEditor overlapping with other views this is how it looks, and it does not change dynamically.
How it looks when the app launches
How it looks when you type text
Here's an example, using your original code with the Button next to the TextEditor. The TextEditor grows until it hits the limit, defined by maxHeight. It also has a view for the messages (since you mentioned iMessage), but you could easily remove that.
struct ContentView: View {
#State private var textEditorHeight : CGFloat = 100
#State private var text = "Testing text. Hit a few returns to see what happens"
private var maxHeight : CGFloat = 250
var body: some View {
VStack {
VStack {
Text("Messages")
Spacer()
}
Divider()
HStack {
ZStack(alignment: .leading) {
Text(text)
.font(.system(.body))
.foregroundColor(.clear)
.padding(14)
.background(GeometryReader {
Color.clear.preference(key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height)
})
TextEditor(text: $text)
.font(.system(.body))
.padding(6)
.frame(height: min(textEditorHeight, maxHeight))
.background(Color.black)
}
.padding(20)
Button(action: {}) {
Image(systemName: "plus.circle")
.imageScale(.large)
.foregroundColor(.primary)
.font(.title)
}.padding(15).foregroundColor(.primary)
}.onPreferenceChange(ViewHeightKey.self) { textEditorHeight = $0 }
}
}
}
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
I am developing an App that supports multiple Profiles. I really like the way Apple displays the Profile Icon next to the Large Navigation Bar Title in all their Apps. See the Screenshot below:
My Question is the following:
Is it possible to achieve this in SwiftUI? And if so, how?
If it's not possible in pure SwiftUI, how can I achieve it including UIKit Code?
Thanks for your help.
I solved this by using SwiftUI-Introspect, to "Introspect underlying UIKit components from SwiftUI".
Here is an example of a view:
struct ContentView: View {
#State private var lastHostingView: UIView!
var body: some View {
NavigationView {
ScrollView {
ForEach(1 ... 50, id: \.self) { index in
Text("Index: \(index)")
}
.frame(maxWidth: .infinity)
}
.navigationTitle("Large title")
.introspectNavigationController { navController in
let bar = navController.navigationBar
let hosting = UIHostingController(rootView: BarContent())
guard let hostingView = hosting.view else { return }
// bar.addSubview(hostingView) // <--- OPTION 1
// bar.subviews.first(where: \.clipsToBounds)?.addSubview(hostingView) // <--- OPTION 2
hostingView.backgroundColor = .clear
lastHostingView?.removeFromSuperview()
lastHostingView = hostingView
hostingView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
hostingView.trailingAnchor.constraint(equalTo: bar.trailingAnchor),
hostingView.bottomAnchor.constraint(equalTo: bar.bottomAnchor, constant: -8)
])
}
}
}
}
Bar content & profile picture views:
struct BarContent: View {
var body: some View {
Button {
print("Profile tapped")
} label: {
ProfilePicture()
}
}
}
struct ProfilePicture: View {
var body: some View {
Circle()
.fill(
LinearGradient(
gradient: Gradient(colors: [.red, .blue]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: 40, height: 40)
.padding(.horizontal)
}
}
The .frame(width: 40, height: 40) & hostingView.bottomAnchor constant will need to be adjusted to your needs.
And the results for each option (commented in the code):
Option 1
Option 2
View sticks when scrolled
View disappearing underneath on scroll
Without NavigationView
I done this with pure SwiftUI. You have to replace the Image("Profile") line with your own image (maybe from Assets or from base64 data with UIImage).
HStack {
Text("Apps")
.font(.largeTitle)
.fontWeight(.bold)
Spacer()
Image("Profile")
.resizable()
.scaledToFit()
.frame(width: 40, height: 40)
.clipShape(Circle())
}
.padding(.all, 30)
This products following result:
With NavigationView
Let's assume that you have NavigationView and inside that there's only ScrollView and .navigationTitle. You can add that profile image there by using overlay.
NavigationView {
ScrollView {
//your content here
}
.overlay(
ProfileView()
.padding(.trailing, 20)
.offset(x: 0, y: -50)
, alignment: .topTrailing)
.navigationTitle(Text("Apps"))
}
Where ProfileView could be something like this:
struct ProfileView: View {
var body: some View {
Image("Profile")
.resizable()
.scaledToFit()
.frame(width: 40, height: 40)
.clipShape(Circle())
}
}
The result will be like this...
...which is pretty close to the App Store:
Code to set underline,I want to make the space between the text and the underline larger.
Text("underline text")
.underline()
Underline is a font feature, you can do custom under just by drawing line anywhere needed
var body: some View {
HStack {
Text("Before")
Text("underline text")
.overlay(
Rectangle().frame(height: 1).offset(y: 4)
, alignment: .bottom)
Text("after.")
}
}
How about use a custom view instead of .underline ?
struct MyUnderline: View {
let color: Color = .black
let height: CGFloat = 1
var body: some View {
Rectangle()
.fill(color)
.frame(height: height)
}
}
Text("underline text")
MyUnderline()
.padding(.top, -10)
You could create a custom view that takes the text and underline padding as parameters
struct UnderlinedText: View {
var text: String
var underlinePadding: CGFloat
var body: some View {
VStack (spacing: underlinePadding) {
Text(text)
GeometryReader { proxy in
Rectangle()
.frame(width: proxy.size.width, height: 1)
}
}
}
}
And use it as follows
UnderlinedText(text: "Hello underlined text", underlinePadding: 10.0)