I have an app full of stories, and I want to see which stories users are reading.
I already installed Firebase Analytics, but all I'm seeing is this kind of content:
I want to be able to see the specific title of the stories users are reading.
This is the ForEach loop I'm using:
ForEach(modelData.stories.shuffled()) { item in
if item.paid == false {
NavigationLink {
StoryDetails(story: item)
} label: {
GeneralCard(isLocked: false, story: item)
}
}
}
And on StoryDetails I have this code, I would like to see the title of the story inside Firebase Analytics.
VStack (spacing: 0) {
Text(story.title)
.font(AppFont.detailTitle())
.bold()
.multilineTextAlignment(.center)
.foregroundColor(.mainColor)
Text(story.category.rawValue)
.font(AppFont.detailDescription())
.multilineTextAlignment(.center)
.padding()
.foregroundColor(.mainColor)
VStack(alignment: .leading, spacing: 20) {
ForEach(story.text, id: \.self) { item in
VStack {
Text(item)
.font(AppFont.detailText())
}
}
}
//.padding(.horizontal, 10)
}
Is this something that can be achieved?
Thanks in advance
You could set this up by manually logging your screenviews instead. For SwiftUI, it is something like this:
struct ContentView: View {
var body: some View {
Text("Hello, world!")
// Logging screen name with class and a custom parameter.
.analyticsScreen(name: "main_content",
class: "ContentView",
extraParameters: ["my_custom_param": 5])
// OR Logging screen name only, class and extra parameters are optional.
.analyticsScreen(name: "main_content")
}
}
Related
I have a set of seven pages. I have made my own scrollable view.
I have a working solution where I have the current page and attach gestures to it like this...
switch page {
case .EyeTest:
EyeTestView(sd: $sd)
.gesture(DragGesture(minimumDistance: swipeMin)
.onEnded { value in
if value.translation.width < -swipeMin { page = Page.Tone }
} )
...`
This worked. It was rather clunky, but I could animate the drags better if I worked at it. However, the tab view worked beautifully with TabView for up to five entries. So, I thought I might be able to span the solution using two tab views. Here is what I did, concluding the custom scrollbar at the top...
VStack() {
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack() {
pageButton(Page.EyeTest, "eyeglasses", "EyeTest", proxy)
pageButton(Page.Tone, "pause.rectangle", "Tone", proxy)
pageButton(Page.Chart, "square.grid.4x3.fill", "Chart", proxy)
pageButton(Page.ByEye, "eye", "ByEye", proxy)
pageButton(Page.List, "list.bullet", "List", proxy)
pageButton(Page.Camera, "camera", "Camera", proxy)
pageButton(Page.Settings, "gear", "Settings", proxy)
}
}
.onAppear { proxy.scrollTo(Page.ByEye, anchor: .center) }
.onChange(of: page) { page in
withAnimation {
proxy.scrollTo(page, anchor: .center)
}
}
}
if (page == Page.EyeTest || page == Page.Tone || page == Page.Chart) {
TabView(selection:$page) {
EyeTestView(sd: $sd).tag(Page.EyeTest)
ToneView(sd: $sd).tag(Page.Tone)
ChartView(sd: $sd).tag(Page.Chart)
ByEyeView(sd: $sd).tag(Page.ByEye)
ListView(sd: $sd).tag(Page.List)
}.tabViewStyle(PageTabViewStyle(indexDisplayMode:.never))
} else {
TabView(selection:$page) {
ChartView(sd: $sd).tag(Page.Chart)
ByEyeView(sd: $sd).tag(Page.ByEye)
ListView(sd: $sd).tag(Page.List)
CameraView(sd: $sd).tag(Page.Camera)
SettingsView(sd: $sd).tag(Page.Settings)
}.tabViewStyle(PageTabViewStyle(indexDisplayMode:.never))
}
}`
However, this does not scroll past the 5th entry.
I would be interested to know why this doesn't work, but I would be happy to replace it with something equivalent that does work. I would also like to know how people handle things like books, which have lost more pages. I feel I ought to make two synchronised scrollable lists.
Yes, maybe it is bad API to have a flat menu with more than five entries. However, some of the ones at the end of the list are rarely used, but need to be discoverable.
I am using Xcode 14.0.1 with iOS 16.0.
Here is a solution that seems to work for me. It is quite close to my original bodge: we have two TabViews, and swap between them depending on the current page so we can always have the neighbours on either side.
I had written my own scrollable index to replace the TabView indexes, but you might make do with the original fittings. I have only been doing Swift for a month, so this may not be the best code, so please post your improvements.
`
enum Page {
case EyeTest
case Tone
case Chart
case ByEye
case List
case Camera
case Settings
}
#AppStorage("menu") private var menu = "Measure"
#State private var page = Page.ByEye
func setMenu(_ page: Page) {
menu = (page == Page.EyeTest || page == Page.Tone || page == Page.Chart ) ? "Test" : "Measure"
}
func pageButton(_ select: Page, _ icon: String, _ title: String, _ proxy: ScrollViewProxy) -> some View {
return Button {
page = select
setMenu(page)
withAnimation {
proxy.scrollTo(select, anchor: .center)
}
} label: {
VStack {
Image(systemName: icon)
Text(title)
}
.frame(width: tabW)
}
.foregroundColor( page == select ? Color.white : Color.gray )
.id(select)
}
var body: some View {
VStack() {
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack() {
pageButton(Page.EyeTest, "eyeglasses", "EyeTest", proxy)
pageButton(Page.Tone, "pause.rectangle", "Tone", proxy)
pageButton(Page.Chart, "square.grid.4x3.fill", "Chart", proxy)
pageButton(Page.ByEye, "eye", "ByEye", proxy)
pageButton(Page.List, "list.bullet", "List", proxy)
pageButton(Page.Camera, "camera", "Camera", proxy)
pageButton(Page.Settings, "gear", "Settings", proxy)
}
}
.onAppear { proxy.scrollTo(page, anchor: .center) }
.onChange(of: page) { page in
withAnimation {
proxy.scrollTo(page, anchor: .center)
}
}
}
if (menu == "Test") {
TabView(selection:$page) {
EyeTestView(sd: $sd).tag(Page.EyeTest)
ToneView(sd: $sd).tag(Page.Tone)
ChartView(sd: $sd).tag(Page.Chart)
ByEyeView(sd: $sd).tag(Page.ByEye)
SettingsView(sd: $sd).tag(Page.Settings)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode:.never))
.onChange(of: page) { page in
setMenu(page)
}
} else {
TabView(selection:$page) {
ChartView(sd: $sd).tag(Page.Chart)
ByEyeView(sd: $sd).tag(Page.ByEye)
ListView(sd: $sd).tag(Page.List)
CameraView(sd: $sd).tag(Page.Camera)
SettingsView(sd: $sd).tag(Page.Settings)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode:.never))
.onChange(of: page) { page in
setMenu(page)
}
}
`
I got this text:
Text("Indem du fortfährst, stimmst du unseren ") +
Text("Nutzungsbedingungen")
.underline()
.foregroundColor(Color("ClickableLink")) +
Text(" und unserer ") +
Text("Datenschutzerklärung")
.underline()
.foregroundColor(Color("ClickableLink")) +
Text(" zu")
I'd like to open a new view using NavigationLink after taping on Nutzungsbedingungen or Datenschutzerklärung, both need to open different views.
I've seen those answers:
SwiftUI tappable subtext
but those are either not what I need or trying them gives me errors and I don't know how to modify them since I'm absolutely new to swift/swiftui
Here are two possible approaches, one using the new NavigationStack with NavigationDestination and the other using popOver.
Both have the downside that you can't concatenate Text views within them. But you can still work with HStack/VStack to define the layout.
enum Legals: Identifiable {
case nutzungsbedingungen
case datenschutzerklärung
var id: Legals { self }
}
struct ContentView: View {
#State private var selected: Legals?
var body: some View {
NavigationStack {
// NavigationLink version
VStack(alignment: .leading) {
Text("Indem du fortfährst, stimmst du unseren")
NavigationLink("Nutzungsbedingungen", value: Legals.nutzungsbedingungen)
Text("und unserer")
NavigationLink("Datenschutzerklärung", value: Legals.datenschutzerklärung)
Text("zu")
}
.navigationDestination(for: Legals.self) { selection in
switch selection {
case .datenschutzerklärung:
Text("Datenschutzerklärung")
case .nutzungsbedingungen:
Text("Nutzungsbedingungen")
}
}
Divider()
// popOver Version
VStack(alignment: .leading) {
Text("Indem du fortfährst, stimmst du unseren ")
Text("Nutzungsbedingungen")
.underline()
.onTapGesture {
selected = .nutzungsbedingungen
}
Text("und unserer ")
Text("Datenschutzerklärung")
.underline()
.onTapGesture {
selected = .nutzungsbedingungen
}
Text("zu")
}
.popover(item: $selected) { selected in
switch selected {
case .datenschutzerklärung:
Text("Datenschutzerklärung")
case .nutzungsbedingungen:
Text("Nutzungsbedingungen")
}
}
}
}
}
Does anyone have a fix for this? I have a Picker in my List that doesn't respond to any user input and is greyed out, but if I move it out of the List and into a VStack it functions normally.
The majority of answers I've found to this question all say that the Picker needs to be in a Form to work (which I don't understand since it works fine in a VStack), or that its a bug in which it only works on a physical device, which I've also tested on and gotten the same result.
I'll provide a screenshot as well as the code I'm using below
import SwiftUI
struct SettingsView: View {
#Binding var age : Int
var body: some View {
VStack(alignment: .leading){
List{
Picker("Your age", selection: $age) {
ForEach(1...100, id: \.self) { number in
Text("\(number)")
}
}
}
}
}
You must embed your List inside a NavigationView, so you can actually "navigate" from "Your age" to the list of Ints.
Just add one additional layer, as shown below:
var body: some View {
VStack(alignment: .leading){
NavigationView { // This is needed to navigate to the ForEach
List {
Picker("Your age", selection: $age) {
ForEach(1..<101) { number in
Text("\(number)")
}
}
}
}
}
}
Using Firebase and Swift, I am attempting to update the "atname" data in Firebase. Currently, the function is currently set up to modify the "username" and "bio" sections in Firebase. The following code is in my SettingsViewModel.swift file:
func updateDetails(field: String){
alertView(msg: "Update \(field)") { (txt) in
if txt != ""{
self.updateBio(id: field == "Name" ? "username" : "bio", value: txt)
}
}
}
func updateBio(id: String, value: String){
ref.collection("Users").document(uid).updateData([
id: value,
]) { (err) in
if err != nil{return}
fetchUser(uid: self.uid) { (user) in
self.userInfo = user
}
}
}
}
"username" is the name inside of the Firebase database. "Name" is added before the ? to provide a better understanding in alertView on what the user is updating.
Here is the code inside my SettingsView.swift file: (The part that is seen on the app)
HStack(spacing: 15){
Text(settingsData.userInfo.username)
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white)
Button(action: {settingsData.updateDetails(field: "Name")}) {
Image(systemName: "pencil.circle.fill") //makes text off centered, figure out how to fix
.font(.system(size: 24))
.foregroundColor(.white)
}
}
HStack(spacing: 15){
Text(settingsData.userInfo.atname)
.foregroundColor(.white)
.fontWeight(.light)
Button(action: {settingsData.updateDetails(field: "Atname")}) {
Image(systemName: "pencil.circle.fill") //makes text off centered, figure out how to fix
.font(.system(size: 24))
.foregroundColor(.white)
}
}
.padding()
HStack(spacing: 15){
Text(settingsData.userInfo.bio)
.foregroundColor(.white)
Button(action: {settingsData.updateDetails(field: "Bio")}) {
Image(systemName: "pencil.circle.fill") //makes text off centered, figure out how to fix
.font(.system(size: 24))
.foregroundColor(.white)
}
}
As you can see, inside of SettingsView.swift, my field for the Atname section is "Atname". However, in SettingsViewModel.swift, I can not figure out how to add Atname into the code to get Atname to actually update in Firebase.
Any time I try to add "Atname" I end up with some kind of error. If launch the app and try to edit "Atname" in settings, it changes "bio". I am relatively new to Swift and I have no idea how to get this to work.
Any help will be appreciated! I can provide more code/details if needed.
Right now, because of the ternary expression that you're using, the only two possibilities for id are "username" or "bio".
I'd suggest refactoring updateDetails so that it can accept more types of input. I see that you're also displaying a message alertView, which looks like the reason you're trying to convert the actual field name into something more human readable -- I added another function called readableNameForField that uses a switch statement to provide the readable names.
What about something like this?
func readableNameForField(_ field : String) -> String {
switch field {
case "username":
return "Name"
case "atname":
return "At Name"
default:
return field.localizedCapitalized
}
}
func updateDetails(field: String){
alertView(msg: "Update \(readableNameForField(field))") { (txt) in
if !txt.isEmpty {
self.updateBio(id: field, value: txt)
}
}
}
Then, at your call sites, you'd use the actual field names:
settingsData.updateDetails(field: "username")
settingsData.updateDetails(field: "atname")
settingsData.updateDetails(field: "bio")
I would like to add a description below a list item, similar to Apple-like lists.
I'm using Xcode 11 for iOS 13.2
NavigationView {
List {
Section(header: Text("Account information")){
Text(User.email)
VStack(alignment: .leading){
Toggle("Daily Digest", isOn: $dailyDigest)
Text("Receive a daily email with the events occurring on that day").font(.system(size: 10))
}
}
}.listStyle(GroupedListStyle()).navigationBarTitle("Settings")
}
The description under the toggle switch, I wish it look like this:
instead of
Try using footer:
NavigationView {
List {
Section(header: Text("Account information"),
footer: Text("Receive a daily email with the events occurring on that day")) { // <- add footer
Text("asd")
VStack(alignment: .leading) {
Toggle("Daily Digest", isOn: $dailyDigest)
}
}
}.listStyle(GroupedListStyle()).navigationBarTitle("Settings")
}