I'm trying to make a notes app with SwiftUI and I'd like to show the notes similar to the Apollo Reddit app does.
The way it shows the post isn't anything special, it just shows the posts using an interface similar to a list with GroupedListStyle(), but with less spacing between sections.
I've tried a lot of tricks to reduce this spacing, but none of them seems to work.
TL;DR
I've got this:
And I want this:
Any help is appreciated. Thanks in advance!
Here's my code:
import SwiftUI
struct NotesView: View {
let array = [
Note(title: "Mi pana miguel letra", content:
"""
[Intro: Keyan JRN & Producer Tag]
El pana Miguel, yah, ey
El pana Miguel, yah, ey (Snorkatje)
Mi pana, mi pana, yeah
Mi pana, mi pana, yeah
Mi pana, mi pana, yeah, eh-eh
Uh-uh-uh-uh-uh-uh
[Estribillo]
Ha-ha-hace un rato conocí al pana Miguel
No-no voy a mentir, se ve bastante fresco
(Ey, tío, ¿conoces a IlloJuan?) ¿Quién?
(IlloJuan) No, que quién te ha preguntado (No-oh)
Ha-hace un rato conocí al pana Miguel (Pana Miguel)
No voy a mentir, se ve bastante fresco (Bastante fresco)
Y el desgraciado de Matías que se vaya ya (Uh-uh, uh, uh)
Prefiero quedarme aquí con mi pana, sentado
"""
),
Note(title: "Note 02", content: "This is a test note."),
Note(title: "Note 03", content: "This is a test note that is supposed to be longer than just 3 lines to test the note preview. Since I cba to write...")
]
#ObservedObject var searchBar: SearchBar = SearchBar()
var body: some View {
NavigationView {
List {
if array.count > 0 {
ForEach(
array.filter
{
searchBar.text.isEmpty ||
$0.id.localizedStandardContains(searchBar.text)
},
id: \.self
) { eachNote in
Section {
NoteView(note: eachNote)
}.buttonStyle(PlainButtonStyle())
}
} else {
NavigationLink(destination: NotesTextEditor()) {
Text("Create a new post")
}
}
}
.listStyle(GroupedListStyle())
.add(self.searchBar)
}
}
}
The possible solution is to use custom a-la group separator, instead of standard.
Tested with Xcode 11.4 / iOS 13.4 on some replicated code.
List {
ForEach(array.indices, id: \.self) { i in
VStack(spacing: 0) {
Text(self.array[i].title)
.padding(.horizontal)
Text(self.array[i].content)
.padding(.horizontal)
if i != self.array.count - 1 { // don't show for last
Rectangle().fill(Color(UIColor.systemGroupedBackground))
.frame(height: 16) // << fit as you need
}
}.listRowInsets(EdgeInsets()) // << avoid extra space
}
}.listStyle(GroupedListStyle())
Related
I'm trying to suss out a problem I'm having. Basically, I have a text field for medical lab values, and I want it to display a symbol when out of range (out of normal medical limits) and another symbol when within normal range. These values are then used in formulae in another view in the app.
This is now my second post on this platform, so please forgive any posting faux pas, I'm trying to adhere to the rules as best as possible re: minimum reproducible and making sure my code is formatted properly in the posting. Here's what I have so far:
import SwiftUI
struct EntryMRE: View {
#Environment(\.managedObjectContext) private var viewContext
#State private var showingResults: Int? = 1
#FocusState private var isTextFieldFocused: Bool
#State var isDone = false
#State var isSaving = false //used to periodically save data
#State var saveInterval: Int = 5 //after how many seconds the data is automatically saved
//DataPoints Chemistry
#State var potassium = ""
var body: some View {
List {
Section(header: Text("🧪 Chemistry")) {
Group {
HStack {
Text("K")
+ Text("+")
.font(.system(size: 15.0))
.baselineOffset(4.0)
Spacer()
TextField("mEq/L", text: $potassium)
.focused($isTextFieldFocused)
.foregroundColor(Color(UIColor.systemBlue))
.modifier(TextFieldClearButton(text: $potassium))
.multilineTextAlignment(.trailing)
.keyboardType(.decimalPad)
if potassium != "" && Int(potassium) != nil {
if Int(potassium)! >= Int(Double(Int(3.5))) && Int(potassium)! <= Int(Double(4.5)) {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color(UIColor.systemGreen))
}
else {
Image(systemName: "exclamationmark.circle.fill")
.foregroundColor(Color(UIColor.systemRed))
}
}
}
}
}
}
}
I've tried making it >= Double(3.5) which then pops the same error and says it should be Int(Double(3.5)) which does allow the code to build, but doesn't actually display the symbol when in range with a decimal (ExhibitA), only with a whole integer. (ExhibitB)
I've added some pictures that'll hopefully help show what I mean.
Thanks in advance!
This fails because you are converting a Double to an Int and vice versa and casting your String to an Int instead of a Double. You are loosing your digits when you do this.
Try:
if let numberValue = Double(potassium) { // Cast String to Double
if (3.5...4.5) ~= numberValue { //Check if casted value is in customRange
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color(UIColor.systemGreen))
}
else {
Image(systemName: "exclamationmark.circle.fill")
.foregroundColor(Color(UIColor.systemRed))
}
}
I am working on a SwiftUI app (Xcode Version 12.4 and iOS 14.4.2) and am having problems to properly handle buttons in a list. I hope someone can point out the way to go.
Here is the relevant code:
struct CustomListView: View {
var localList:[SomeManagedObject], moc:NSManagedObjectContext
#State var showingOtherView = false
#State var selectNbr:Int!
func handleCustomItem(_ argument: SomeManagedObject) {
print(#function+" (1):\(showingOtherView):")
selectNbr = localList.firstIndex(of: argument)
self.showingOtherView.toggle()
print(#function+" (2):\(showingOtherView):\(selectNbr):")
..... Do useful things .....
}
var body: some View {
List {
ForEach(self.localList) {
item in
HStack {
Spacer()
Button(action: {
self.handleCustomItem(item)
})
{
Text(item.expression!)
.foregroundColor(Color.red))
.font(.headline)
.padding(.horizontal, 11).padding(.vertical, 15)
}
Spacer()
}
}
}.sheet(isPresented: $showingOtherView) {
OtherView(parameter: localList[selectNbr])
}
}
}
When I run the app this is what I get as ouput, which is what I expect:
handleCustomItem(_:) (1):false:
handleCustomItem(_:) (2):true:Optional(4):
Then I have a break point before running this line of code (to avoid a crash):
OtherView(parameter: localList[selectNbr])
This is where things get weird (unexpected for me), in the debugger I can see:
(lldb) p showingOtherView
(Bool) $R0 = false
(lldb) p selectNbr
(Int?) $R2 = nil
(lldb)
But I would expect showingOtherView to be true, and (more important) selectNbr to hold the value 4. What is going on here that I am missing ?
the SwiftUI App I'm writing has large blocks of text, so I'm trying to create a "markup language" I can use in my JSON files that hold the text, to define when a word should be bolded. The issue is, I can't seem to stop new text blocks going to a different line.
Here is the relevant code as I currently have it.
struct formattedText: View {
var text: String
var body: some View {
let split = text.components(separatedBy: "**")
Group {
ForEach(split, id: \.self) { line in
if line.hasPrefix("$$") {
Text(line.trimmingCharacters(in: CharacterSet(charactersIn: "$")))
.bold()
} else {
Text(line)
}
}
}
.lineLimit(nil)
.multilineTextAlignment(.leading)
}
Using this code, I can put a **$$ before a word and ** after, to define it as bold.
Only issue is every time I bold a word it goes to a new line. I know the traditional way to fix this is:
Text("Simple ") + Text("Swift ") + Text("Guide")
This does not work with my ForEach loop though. Any suggestions?
ForEach creates separate Views. You really just want one Text, so you mean a for...in loop:
var body: some View {
let split = text.components(separatedBy: "**")
var result = Text("")
for line in split {
if line.hasPrefix("$$") {
result = result + Text(line.trimmingCharacters(in: CharacterSet(charactersIn: "$")))
.bold()
} else {
result = result + Text(line)
}
}
return result
.lineLimit(nil)
.multilineTextAlignment(.leading)
}
Since you may want a lot more things than just bold, you might find it useful to extract that part into its own function or collection of functions:
private func applyAttributes(line: String) -> Text {
if line.hasPrefix("$$") {
return Text(line.trimmingCharacters(in: CharacterSet(charactersIn: "$")))
.bold()
} else {
return Text(line)
}
}
With that, constructing this is simpler:
var body: some View {
text.components(separatedBy: "**")
.map(applyAttributes)
.reduce(Text(""), +)
.lineLimit(nil)
.multilineTextAlignment(.leading)
}
I'm trying to run through a list of items that all have a field called "yes" which is an integer. Essentially this loop runs through and if the yes field for an item is more than 0, it will show up, and if it is 0 it won't. Works as I want it to so far but what I would like to do is show a different message if ALL items have a 0 value, so that the section of the screen is not simply empty. How would I go about doing this? I tried putting the message into the "else" but (obviously) it just repeated the message the amount of times there are items in the db.
ForEach(items.indices, id: \.self) { i in
if (items[i].yes != 0) {
HStack {
Text(items[i].name)
Spacer()
Text("\(items[i].yes)")
}
Divider()
} else {}
}
You need something like below (typed in place, so might be typos):
if items.filter({ $0.yes != 0}).isEmpty {
Text("Message for ALL are 0")
} else {
ForEach(items.indices, id: \.self) { i in
if (items[i].yes != 0) {
HStack {
Text(items[i].name)
Spacer()
Text("\(items[i].yes)")
}
Divider()
} else {}
}
}
I'm learning to use meteor.
Is it possible to easily localize accounts.ui components?
This is a slightly hacky solution, you could remove the accounts-ui package you have and add it in manually. Find the accounts-ui branch on meteor's github repo and find all the contents in the accounts-ui package such as: login_buttons_dialogs.html, login_buttons.html, etc.
You could then edit and copy these files into your meteor project directly with the custom text in the language you would like.
Meteor hasn't implemented internationalization but its on the roadmap. What you could do is use meteorite and install a localization package such as simple-i18n in conjunction with the manually edited accounts-ui files to give your users a multi-lingual experience or simply offer accounts-ui in a different language.
Here is a trick I have used. Simple, but works:
Template.header.rendered = function() {
$('#login-sign-in-link').text('Přihlásit se ▾');
$('.login-close-text').text('Zavřít nabídku');
$('.sign-in-text-google').text('Přihlásit se přes Google');
$('.sign-in-text-facebook').text('Přihlásit se přes FB');
//etc...
};
For portuguese (PT-BR)
Template.layout.rendered = function() {
$('#login-sign-in-link').text('Login ▾');
$('.login-close-text').text('Fechar');
$('#login-username-or-email-label').text('Nome de usuário ou e-mail');
$('#login-password-label').text('Senha');
$('#signup-link').text('Criar uma conta');
$('#forgot-password-link').text('Esqueceu a senha?');
$('#login-buttons-forgot-password').text('Recuperar');
$('#back-to-login-link').text('Login');
$('#login-username-label').text('Usuário para login');
$('#login-buttons-open-change-password').text('Alterar senha');
$('#login-buttons-logout').text('Logout');
if ($('#login-buttons-password').text().indexOf('Sign in') != -1) {
$('#login-buttons-password').text('Login');
} else {
$('#login-buttons-password').text('Criar conta');
}
$('.login-button').addClass('btn btn-warning');
$('.login-button').removeClass('login-button login-button-form-submit');
if ($('.message.error-message').text().indexOf('Username must be at least 3 characters long') != -1) {
$('.message.error-message').text('Usuário deve ter pelo menos 3 caracteres');
} else if ($('.message.error-message').text().indexOf('Incorrect password') != -1 || $('.message.error-message').text().indexOf('User not found') != -1) {
$('.message.error-message').text('Usuário/senha errado(s)');
}
$('#login-old-password-label').text('Senha atual');
$('#login-buttons-do-change-password').text('Alterar a senha');
$('#reset-password-new-password-label').text('Nova senha');
$('#login-buttons-reset-password-button').text('Alterar');
if ($('.message.info-message').text().indexOf('Email sent') != -1) $('.message.info-message').text('E-mail enviado');
$('#just-verified-dismiss-button').parent().html('Email verificado <div class="btn btn-warning" id="just-verified-dismiss-button">Ocultar</div>');
};
WATCH OUT BOYS!
The CLOSE in the $('.login-close-text') is the VERB... TO CLOSE
It does not mean "NEAR" as I may have read in some answers above.
For french:
/**
* Accounts-ui ugly translation
* TODO : use i18n solution
*/
Template.header.rendered = function() {
$('#login-sign-in-link').text('Connexion ▾');
$('.login-close-text').text('Fermer');
$('#login-username-or-email-label').text('Pseudo ou email');
$('#login-password-label').text('Mot de passe (mdp)');
$('#signup-link').text('Créer un compte');
$('#forgot-password-link').text('Mdp oublié');
$('#login-buttons-forgot-password').text('Récupération');
$('#back-to-login-link').text('Connexion');
$('#login-username-label').text('Pseudo');
$('#login-buttons-open-change-password').text('Changer de mdp');
$('#login-buttons-logout').text('Deconnexion');
if ($('#login-buttons-password').text().indexOf('Sign in') != -1) {
$('#login-buttons-password').text('Connexion');
} else {
$('#login-buttons-password').text('Créer le compte');
}
$('.login-button').addClass('btn btn-warning');
$('.login-button').removeClass('login-button login-button-form-submit');
if ($('.message.error-message').text().indexOf('Username must be at least 3 characters long') != -1) {
$('.message.error-message').text('Le login doit faire plus de 3 caractères');
} else if ($('.message.error-message').text().indexOf('Incorrect password') != -1 || $('.message.error-message').text().indexOf('User not found') != -1) {
$('.message.error-message').text('login ou mot de passe incorrect');
}
$('#login-old-password-label').text('Mot de passe actuel');
$('#login-buttons-do-change-password').text('Changer le mot de passe');
$('#reset-password-new-password-label').text('Nouveau mot de passe');
$('#login-buttons-reset-password-button').text('Changer');
if ($('.message.info-message').text().indexOf('Email sent') != -1) $('.message.info-message').text('Email envoyé');
$('#just-verified-dismiss-button').parent().html('Email vérifié <div class="btn btn-warning" id="just-verified-dismiss-button">Masquer</div>');
};
i18n is still on the todo list of Meteor. Meanwhile, you can cook a system of your own.
Se how they achieved internationalisation here for example: https://github.com/bolora/multi-page-config
For German, including all labels.
This is mostly adapted from Pascoual's post, however, the buttons are still formatted and some additional labels are translated...
If you find a label which still isn't translatet, please report this in a comment.
/**
* Accounts-ui ugly translation
* TODO : use i18n solution
*/
Template.login.rendered = function() {
$('#login-sign-in-link').text('Einloggen');
$('.login-close-text').text('Schliessen');
$('#login-username-or-email-label').text('Benutzername oder Email');
$('#login-password-label').text('Passwort');
$('#signup-link').text('Konto erstellen');
$('#forgot-password-link').text('Passwort vergessen');
$('#login-buttons-forgot-password').text('Wiederherstellen');
$('#back-to-login-link').text('Zurück');
$('#login-username-label').text('Benutzername');
$('#login-buttons-open-change-password').text('Passwort ändern');
$('#login-buttons-logout').text('Logout');
$('#reset-password-new-password-label').text('Neues Passwort');
$('#login-old-password-label').text('Aktuelles Passwort');
$('#login-password-label').text('Neues Passwort');
$('#login-buttons-do-change-password').text('Passwort ändern');
if ($('#login-buttons-password').text().indexOf('Sign in') != -1) {
$('#login-buttons-password').text('Einloggen');
} else {
$('#login-buttons-password').text('Konto erstellen');
}
if ($('.message.error-message').text().indexOf('Username must be at least 3 characters long') != -1) {
$('.message.error-message').text('Benutzername muss mindestens 3 Zeichen lang sein');
} else if ($('.message.error-message').text().indexOf('Incorrect password') != -1 || $('.message.error-message').text().indexOf('User not found') != -1) {
$('.message.error-message').text('Benutzername oder Passwort falsch');
}
};
The simplest way is to user accounts-tap package:
https://atmospherejs.com/softwarerero/accounts-t9n
It is based on tapi18n, you have just to add the following configuration on client side :
T9n.setLanguage('<yourLanguage>')
You can consider to use meteor-accounts-ui-bootstrap-3
It supports localization:
accountsUIBootstrap3.setLanguage('ru');