Jetpack Compose Button goes above keyboard? - keyboard

I have some content and a TextField, and a button on the bottom of the screen.
When I tap on the TextField, the keyboard covers my button, but obviously I want the button to go above the keyboard.
How can I do that?
I already added this line to my MainActivity, before setContent():
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
My Scaffold:
Scaffold { padding ->
Box(
modifier = Modifier
.padding(20.dp)
.padding(top = 40.dp)
.fillMaxSize()
) {
Column {
Text("My content")
Text("My content")
Text("My content")
TextField(
value = text,
onValueChange = { text = it },
)
}
Box(
modifier = Modifier.align(Alignment.BottomCenter)
) {
Button(content = { Text("BTN") }, onClick = {})
}
}
}

Add android:windowSoftInputMode="adjustResize" to the AndroidManifest.xml. You're cured!

Related

How to select an item from a search file and place in textfield in another file

Using SwiftUI - Xcode 14.2 - iOS 16.0
I have tried different search tutorials to create a search file for my project but am unable to find out how to select the item in the search file and place that selected item in a textfield in another file. I have searched this site for other posts, i tried searching through Google, YouTube, etc...
In File 1, I have a textfield that that has a prompt 'start typing' and when selected, it directs you to the Search file to select the item you want, so it can be placed in place of the prompt.
File 1 (where the textfield is needed to paste the selected item):
VStack {
NavigationLink(destination: NameSearch()) {
TextField("Name", text: .constant(""), prompt: Text(" Start typing ")
.foregroundColor(.blue))
.multilineTextAlignment(.leading)
.padding()
}
}
Once I click on the 'start typing' prompt, it navigates to NameSearch.swift file, as seen below.
NameSearch.swift:
import SwiftUI
struct NameSearch: View {
let name = [
"Jane", "George", "Sam", "Henry", "Sally", "Liz", "John"
]
#State private var searchText = ""
var body: some View {
NavigationStack {
VStack {
// Search view
SearchBarView(searchText: $searchText)
List {
// Filtered list of names
ForEach(name.filter{$0.hasPrefix(searchText) || searchText == ""}, id:\.self) {
searchText in Text(searchText)
}
}
.navigationBarTitle(Text("Search Name"))
.resignKeyboardOnDragGesture()
}
}
}
}
struct NameSearch_Previews: PreviewProvider {
static var previews: some View {
Group {
NameSearch()
.environment(\.colorScheme, .light)
NameSearch()
.environment(\.colorScheme, .dark)
}
}
}
extension UIApplication {
func endEditing(_ force: Bool) {
self.windows
.filter{$0.isKeyWindow}
.first?
.endEditing(force)
}
}
struct ResignKeyboardOnDragGesture: ViewModifier {
var gesture = DragGesture().onChanged{_ in
UIApplication.shared.endEditing(true)
}
func body(content: Content) -> some View {
content.gesture(gesture)
}
}
extension View {
func resignKeyboardOnDragGesture() -> some View {
modifier(ResignKeyboardOnDragGesture())
}
}
struct SearchBarView: View {
#Binding var searchText: String
#State private var showCancelButton: Bool = false
var onCommit: () ->Void = {print("onCommit")}
var body: some View {
HStack {
HStack {
Image(systemName: "magnifyingglass")
// Search text field
ZStack (alignment: .leading) {
if searchText.isEmpty { // Separate text for placeholder to give it the proper color
Text("Search")
}
TextField("", text: $searchText, onEditingChanged: { isEditing in
self.showCancelButton = true
}, onCommit: onCommit).foregroundColor(.primary)
}
// Clear button
Button(action: {
self.searchText = ""
}) {
Image(systemName: "xmark.circle.fill").opacity(searchText == "" ? 0 : 1)
}
}
.padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
.foregroundColor(.secondary) // For magnifying glass and placeholder test
.background(Color(.tertiarySystemFill))
.cornerRadius(10.0)
if showCancelButton {
// Cancel button
Button("Cancel") {
UIApplication.shared.endEditing(true) // this must be placed before the other commands here
self.searchText = ""
self.showCancelButton = false
}
.foregroundColor(Color(.systemBlue))
}
}
.padding(.horizontal)
.navigationBarHidden(showCancelButton)
}
}
Question 1: How do I hide all the names from showing in the list so that I just see the search bar and the cancel button and an empty list?
Question 2: Once I type the name I am looking for, it should pop up and I want to select name - how can I do this?
once I type the name in search bar, it appears in the empty list
I select that name
it then takes me back to File 1
replaces the 'start typing' prompt with the name i just selected in the Search file.
Question 3: I have noticed in the Search file, I am getting a warning with the following code. How can I resolve it?
extension UIApplication {
func endEditing(_ force: Bool) {
self.windows
.filter{$0.isKeyWindow}
.first?
.endEditing(force)
}
}
The warning that appears is:
'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a
relevant window scene instead
Firstly, thank you for providing a working example of your code.
As you're building for iOS 15+, you should probably be using the .searchable modifier rather than rolling your own.
The 2021 WWDC video introducing this feature is here https://developer.apple.com/wwdc21/10176
Some new features from 2022 here: https://developer.apple.com/wwdc22/10052

Restore keyboard after rotation in Jetpack Compose

I have a TextField on one of my app screens. When I rotate my device the text field retains the value, but not the soft keyboard focus.
How could I keep the focus and prevent the keyboard from disappearing?
Here is a simplified version of the composable for the screen:
#Composable
fun LoginScreen(
uiState: LoginUiState,
) {
MyTheme {
Surface(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollableState)
.imePadding(),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
value = uiState.email,
enabled = !uiState.isLoggingIn
)
}
}
}
}
The UI state comes from the model.
You are using the a state-preserver like a ViewModel here, I suppose. You could either store the value in a rememberSaveable block, as Nikola suggests, or you could simply put a simple Boolean where you put the uiState parameter. There's no need to use MutableState<T> this way. Also, no side-effects are required. Just create a parameter.
#Composable
fun MyFiled(
loginState: ... ,
isFocused: Boolean
){
if (isFocused)
focusRequestor.requestFocus()
...
}
Just put a simple condition, and it'll do.
You need to use rememberSaveable to store wither the TextField was focused previously.
val focusRequester = remember { FocusRequester() }
var hasFocus by rememberSaveable { mutableStateOf(false) }
TextField(
value = ...,
onValueChange = { ... },
modifier = Modifier.focusRequester(focusRequester).onFocusChanged {
hasFocus = it.hasFocus
}
)
LaunchedEffect(hasFocus){
if(hasFocus) {
focusRequester.requestFocus()
}
}

How to fix TextField value characters re-arranging when typing in jetpack compose

When I am entering data on the Textfield and happen to press backspace to remove the last letter, then try to type again, the text in the text field rearranges.
I have copied code from jetpack compose play ground and tried it, but it is behaving the same. Is their any fix for this?
Here is my code:
#Composable
fun TextFieldDemo() {
Column(Modifier.padding(16.dp)) {
val textState = remember { mutableStateOf(TextFieldValue()) }
TextField(
value = textState.value,
onValueChange = { textState.value = it }
)
Text("The textfield has this text: " + textState.value.text)
}
}

Placeholder Photo Not Appearing on Null URL in Search - Android, Kotlin, Glide

I'm using Glide in an Android, Kotlin, Jetpack project. When the photos are loading, the DEFAULT_MOVIE_IMAGE appears. However if one of the movies that is returned has a null value for an image URL, no image appears and the card shrinks down to the size of only the title. I'm trying to set it so the DEFAULT_MOVIE_IMAGE will appear if there is a null value for the movie poster url.
Setting a placeholder or an error image does not appear to be working.
Image part of the MovieCard composable
movie.posterPath?.let { url ->
val image = loadPicture(url = url, defaultImage = DEFAULT_MOVIE_IMAGE).value
image?.let { img ->
Image(
bitmap = img.asImageBitmap(),
contentDescription = "Movie Projector",
modifier = Modifier
.fillMaxWidth()
.height(450.dp),
contentScale = ContentScale.Fit
)
}
ImageUtils.kt
const val DEFAULT_MOVIE_IMAGE = R.drawable.movie_placeholder
const val POSTER_BASE_URL = "https://image.tmdb.org/t/p/w500"
#Composable
fun loadPicture(
url: String,
#DrawableRes defaultImage: Int
): MutableState<Bitmap?> {
val bitmapState: MutableState<Bitmap?> = remember { mutableStateOf(null) }
Glide.with(LocalContext.current)
.asBitmap()
.load(defaultImage)
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
bitmapState.value = resource
}
override fun onLoadCleared(placeholder: Drawable?) {
}
})
Glide.with(LocalContext.current)
.asBitmap()
.load("https://image.tmdb.org/t/p/w500$url")
.error(R.drawable.space_dog_laika1)
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
bitmapState.value = resource
}
override fun onLoadCleared(placeholder: Drawable?) {
}
})
return bitmapState
}
I ended up wrapping the image piece of the movie card in an if/else statement. I'm not sure if this is best practice or the most terse solution, but it worked.
if (movie.posterPath != null) {
movie.posterPath?.let { url ->
val image = loadPicture(url = url, defaultImage = DEFAULT_MOVIE_IMAGE).value
image?.let { img ->
Image(
bitmap = img.asImageBitmap(),
contentDescription = "Movie Poster",
modifier = Modifier
.fillMaxWidth()
.height(450.dp),
contentScale = ContentScale.Fit
)
}
}
} else {
val image: Painter = painterResource(id = DEFAULT_MOVIE_IMAGE)
Image(
painter = image,
contentDescription = "Film Projector",
modifier = Modifier
.fillMaxWidth()
.height(450.dp),
contentScale = ContentScale.Fit
)
}

Tab to next text field when 'next/return' key hit on keyboard in SwiftUI

This is strictly for SwiftUI.
I would like to have the keyboard move to the next available text field when the user hits the 'return' key on the keyboard.
I have the following view:
var body: some View {
VStack {
NavigationView {
Form {
TextField("First name", text: $model.firstname)
.tag(1)
TextField("Last name", text: $model.lastname)
.tag(2)
}
.navigationBarTitle("Add a Person", displayMode: .inline)
}
}
}
And the following code that should allow the tab:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let nextTag = textField.tag + 1
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
nextResponder.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return true
}
I am just not sure how to implement it in SwiftUI?
How do I assign it to the delegate of the textfield?!
****UPDATE****
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
print("Current Tag: ", textField.tag) // Works correctly
let nextTag = textField.tag + 1
print("Next Tag: ", nextTag) // Works correctly
let nextResponder = textField.superview?.viewWithTag(nextTag) as UIResponder? // ALWAYS RETURN NIL
....
Not sure why the assignment of nextResponder always returns nil?!
iOS15+
Now it can be easily done with FocusState+.focused(,equals:) specifying named tags and updating focus state on needed action.
Tested with Xcode 13.3 / iOS 15.4
Here is main part:
#FocusState private var infocus: Field?
enum Field {
case first, last
}
// ...
TextField("First name", text: $firstname,
onCommit: { infocus = .last }) // << here !!
.focused($infocus, equals: .first)
Complete test module in project is here

Resources