I'm seeking some help for my iOS app and hope someone can get me on the right path!
FYI: I'm no developper, I'm an architectural student trying to work on a project for a museum.
So, I have this view which contains multiple cards for different objects that are in the museum and when I tap on it, I go to a new view with it's image, description,... + a PlayButton (the Playbutton is a vectorial triangle with some overlay for it's frame, background..).
I have it configured in a way for me to be able to add easily more objects with it corresponding informations with a dictionary.
struct Card: Identifiable {
let id = UUID()
var index: Int
var title: String
var subtitle: String
var description: String
var text: String
var image: String
var background: String
var logo: String }
var cards = [
Card(index: 1, title: "Universale".uppercased(), subtitle: "Joe Colombo".uppercased(), description: "Short description", text: "Long description", image: "Universale", background: "Background 5", logo: "Logo 5"),]
Then I call for this variable in my main view with this code to see every cards I want to show.
var featured: some View {
ScrollView(.horizontal, showsIndicators: false){
HStack(spacing: 16) {
ForEach(cards) { cards in
CardsItem(cards: cards)
.onTapGesture {
showCourse = true
selectedCourse = cards
}
.accessibilityElement(children: .combine)
.accessibilityAddTraits(.isButton)
}
}
.padding(20)
}
.tabViewStyle(.page(indexDisplayMode: .never))
.sheet(isPresented: $showCourse) {
CardsView(namespace: namespace, cards: $selectedCourse, isAnimated: false)
}
}
And my complete view is coded like this:
var content: some View {
ScrollView {
scrollDetection
Spacer()
.padding(.top, 40)
Text("Essayez les objets de la collection chez vous!")
.font(.body.bold())
.foregroundColor(.primary)
.sectionTitleModifier()
Group {
featured
Text("Porte-à-faux".uppercased())
.font(.body.bold())
.foregroundColor(.secondary)
.sectionTitleModifier()
featured2
.padding(.bottom, 60)
}
}
.coordinateSpace(name: "scroll")
.overlay(NavigationBar(title: "Collection", contentHasScrolled: $contentHasScrolled))
}
At this point, everything works like I want. But now I want to add a way for each object to access a usdz file and show it in AR QuickLook when I tap on the play button. I managed to get it working (Kinda, I currently need to kill the app if I want to go back afterwards) with one file that launches on every cards but I can't find a way to launch the corresponding usdz file for each object.
How would I approach this? I hope I was clear enough.
Thanks in advance!
Here's the code I used for the AR QuickLook
class ARQLViewController: UIViewController, QLPreviewControllerDataSource {
let assetName = "Pantone"
let assetType = "usdz"
let allowsContentScaling = false
let canonicalWebPageURL = URL(string: "nil")
override func viewDidAppear(_ animated: Bool) {
let previewController = QLPreviewController()
previewController.dataSource = self
present(previewController, animated: true, completion: nil)
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
guard let path = Bundle.main.path(forResource: assetName, ofType: assetType) else {
fatalError("Couldn't find the supported asset file.")
}
let url = URL(fileURLWithPath: path)
let previewItem = ARQuickLookPreviewItem(fileAt: url)
previewItem.allowsContentScaling = allowsContentScaling // default = true
previewItem.canonicalWebPageURL = canonicalWebPageURL // default = nil
return previewItem
}}
Related
Hello there i am trying to learn SwiftUI and my version is 16.0 i have a problem with transferring data between two pages. My situation is i want a list which contains my elements like : Image, Text etc. then when user clicks to an item. I want to send him or her detail page which gives selected item : Image, Text. I hope i clarified my problem with my explanation.
First of all Here is my list and list item :
struct WebViewCard: View {
#EnvironmentObject var dataManager : DataManager
#Environment(\.dismiss) var dismiss
var body: some View {
List {
ForEach(dataManager.zodiacNews, id: \.id){ zodiacNews in
HStack(alignment: .top) {
VStack(alignment: .leading,content: {
WebImage(url: URL(string: zodiacNews.image))
.resizable()
.scaledToFit()
.frame(width: 300, height: 170)
.cornerRadius(10)
.shadow(radius: 10)
VStack(alignment: .leading, spacing: 5) {
Text(zodiacNews.title)
.foregroundColor(.primary)
.font(.headline)
Text(zodiacNews.subTitle)
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
.lineLimit(2)
.frame(height: 40)
}
}).shadow(color: Color(red:0,green: 0,blue: 0,opacity: 0.3), radius: 3,x: 6,y: 6)
}
}
}.background {
Color.white
}.navigationTitle("Burçlar")
}
}
Second page is where i am putting this view :
struct OpenWebPageList: View {
#EnvironmentObject var dataManager : DataManager
var body: some View {
let allNews = self.dataManager.zodiacNews
NavigationView {
NavigationLink(destination: OpenWebPage(news: allNews)){
WebViewCard()
}
}
}}
Third one will be My Detail View ->
struct OpenWebPage: View {
var news : [BurcWebViewDataClass]
var body: some View {
ForEach(news) { news in
Text(news.title)
}
}}
and finally my datamanager code is below:
class DataManager : ObservableObject {
#Published var zodiacs : [ZodiacData] = []
#Published var zodiacNews : [BurcWebViewDataClass] = []
init(){
fetchZodiacNews()
}
func fetchZodiacNews(){
zodiacNews.removeAll()
let db = Firestore.firestore()
let ref = db.collection("BurcHaberleri")
ref.getDocuments{ snapshot, error in
guard error == nil else {
print(error!.localizedDescription)
return
}
if let snapshot = snapshot{
for document in snapshot.documents{
let data = document.data()
let id = data["id"] as? String ?? ""
let url = data["url"] as? String ?? ""
let image = data["image"] as? String ?? ""
let title = data["title"] as? String ?? ""
let subTitle = data["subTitle"] as? String ?? ""
let zodiacNews = BurcWebViewDataClass(id: id, url: url, image: image, title: title, subTitle: subTitle)
self.zodiacNews.append(zodiacNews)
}
}
}
}
with this coode if user clicks on any item which is in the list. User will see all the items from my firebase-firestore. So i want to resolve this problem. Any help will be preciated.
I am trying to add UI tests to a SwiftUI project.
I have a list, which contains views - those then contain a number of views.
I cannot seem to access the furthest most view in my UI test.
I thought I could add an accessibility identifier to each element but I cannot make my test pass still.
A very simple example;
ContentView
struct ListModel: Identifiable {
let id: String
let text: String
}
struct ContentView: View {
private var state = (0..<50).map { ListModel(id: "\($0)", text: "Row \($0)") }
var body: some View {
List(state, id: \.id) { item in
ContentViewRow(text: item.text)
.accessibility(identifier: "FEED_ITEM")
}
.accessibility(identifier: "FEED")
}
}
struct ContentViewRow: View {
let text: String
var body: some View {
Text(text)
.accessibility(identifier: "CONTENT_ROW_TEXT")
}
}
Tests
class TestingSwiftUIUITests: XCTestCase {
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
let feed = app.tables["FEED"]
XCTAssert(feed.waitForExistence(timeout: 0.5))
let row0 = feed.staticTexts["FEED_ITEM"].firstMatch
XCTAssert(row0.waitForExistence(timeout: 0.5))
let textView = row0.staticTexts["CONTENT_ROW_TEXT"].firstMatch
XCTAssert(textView.waitForExistence(timeout: 0.5)) // <-- This fails.
}
}
How can I access a view inside ContentViewRow - thank you.
identifier: "CONTENT_ROW_TEXT" seems to be overriden by identifier: "FEED_ITEM"
Perhaps you can leave just "FEED_ITEM" and check for the label text if needed.
let row0 = feed.staticTexts["FEED_ITEM"].firstMatch
XCTAssert(row0.waitForExistence(timeout: 0.5))
XCTAssert(row0.label == "Row 0")
I'm trying to check if my array contains the machine learning model output, but its giving me this weird error, and I don't understand it.
Here's my code
Image Detector class:
import SwiftUI
class ImageDetector: ObservableObject {
#Published private var detector = Detector()
var imageClass: String? {
detector.results
}
// MARK: Intent(s)
func detect(uiImage: UIImage) {
guard let ciImage = CIImage (image: uiImage) else { return }
detector.detect(ciImage: ciImage)
}}
Content View:
#ObservedObject var detector: ImageDetector
let collectionArr = ["Test", "test2", "test3", "test4"]
var body: some View {
VStack {
var mlOutput = detector.imageClass
Group{
// Outputing the information of the respective Item
if collectionArr.contains(mlOutput) {
HStack{
Text("")
.font(.caption)
Text(mlOutput)
.bold()
}
}
else {
HStack{
Text("This is not in the array")
.font(.caption)
}
}
}
Since .contains returns a Bool, you don't need to provide a binding to a variable (which is used in cases where you have a statement returning an Optional value).
//remove var mlOutput... line here
Group{
if let mlOutput = detector.imageClass, collectionArr.contains(mlOutput) {
HStack{
Text("").font(.caption)
Text(mlOutput).bold()
}
} else {
HStack {
Text("This is not in the array").font(.caption)
}
}
}
This is assuming that mlOuput (which maybe is a typo for mlOutput?) is a String
I have this SwiftUI view (will remove non-relevant parts)
struct LoginView: View {
var body: some View {
VStack {
Spacer()
InputField(title: "Email", text: $email)
InputField(title: "Password", text: $password, showingSecureField: true)
InputField(title: "Store", text: $userStore)
CallToActionButton(
title: newUser ? "Register User" : "Log In",
action: userAction
)
Button(action: { newUser.toggle() }) {
HStack {
CheckBox(title: "Create Account", isChecked: $newUser)
Spacer()
}
}
Spacer()
}
}
I can write a UI Test for that button with this code:
func testClickLoginButton() throws {
let app = XCUIApplication()
app.launch()
let startButton = app.buttons["Log In"]
XCTAssertTrue(startButton.waitForExistence(timeout: 1))
XCTAssertTrue(startButton.isEnabled)
startButton.tap()
}
And it works, as there's a button with that title on screen.
But then I want to automatically put some text in those InputTexts and test them, but can't find them, as they're neither UITextField nor UITextView. How can I accomplish this? Thanks!
func testAddEmail() throws {
let app = XCUIApplication()
app.launch()
// fails as can't find any text field containing "Email"
let emailText = app.textFields["Email"]
XCTAssertTrue(emailText.waitForExistence(timeout: 1))
emailText.typeText("testemail#realm.com")
XCTAssertEqual(emailText.value as! String, "testemail#realm.com")
}
How about adding:
InputField(title: "Email", text: $email)
.accessibility(identifier: "email_input")
Same for any other element you want to test from UI Tests?
I am not an expert and have seen different ways to do UITests in SwiftUI. There are perhaps better ways with snapshots etc but I quickly recreated your example for fun and added an accessibility identifier:
struct InputField: View {
var title: String
#Binding var email: String
var body: some View {
VStack(alignment: .leading) {
Text("Enter your email ")
TextField(title, text: $email)
.accessibility(identifier: "Email")
}
.padding()
}
}
struct ContentView: View {
#State private var email: String = ""
var body: some View {
InputField(title: "Email: ", email: $email)
}
}
Also it might be sometimes required to add a tap before entering the email or text. It was giving me an error because of this.
The UItest would look like this and is passing :)
func testAddEmail() throws {
let app = XCUIApplication()
app.launch()
let emailText = app.textFields["Email"]
XCTAssertTrue(emailText.waitForExistence(timeout: 1))
emailText.tap()
emailText.typeText("testemail#realm.com")
XCTAssertEqual(emailText.value as! String, "testemail#realm.com")
}
Also a cool trick I learned is to put a breakpoint like this:
and in the debugger at the prompt enter the following:
(lldb) po XCUIApplication().textFields
And so you get the list and you see if identifiers are missing. Same thing for buttons and staticTexts etc.
I have an issue in SwiftUI where my image from a downloadURL doesn't publish to the view for visible items. The SwiftUI elements that are off the view do get updated as I scroll. The issue is that initially, the image URL is "" (blank) when the view renders. Thefore, the default image is rendered, as expected. Although, when the downloadURL task completes, the image is not updated. If I use a combination of willSet and/or objectWillChange.send() on the attribute, 1 image gets updated, but only 1.
High level flow:
Get Series data file from Firebase
Convert series file data to Series objects within the modelView
For each Series object, get the image from Firebase asynchronously
Here is the view code
import SwiftUI
import struct Kingfisher.KFImage
import Firebase
struct SeriesView: View {
// Firebase auth
#EnvironmentObject var authState: AuthenticationState
// Listen for view model state changes, i.e. series
#ObservedObject var seriesList = SeriesList()
private func signoutTapped() {
authState.signout()
}
// search bar
#State var searchText = ""
#State var isSearching = false
// firebase
//#State private var imageURL = URL(string: "")
#State private var imageURLString = ""
let storage = Storage.storage()
// view boday
var body: some View {
NavigationView {
ScrollView {
SearchBar(searchText: $searchText, isSearching: $isSearching)
LazyVGrid(columns: [
GridItem(.flexible(minimum: 100, maximum: 200), spacing: 16, alignment: .top),
GridItem(.flexible(minimum: 100, maximum: 200), spacing: 16, alignment: .top),
GridItem(.flexible(minimum: 100, maximum: 200), spacing: 16)
], alignment: .leading, spacing: 16, content: {
// filter for search entry
ForEach(seriesList.seriesArray//) { series in
.filter({"\($0.uniqueId)".contains(searchText)
|| "\($0.recordName)".contains(searchText)
|| "\($0.seriesName)".contains(searchText)
|| "\($0.fromYear)".contains(searchText)
|| "\($0.toYear)".contains(searchText)
|| searchText.isEmpty}), id: \.self) { series in
SeriesInfo(series: series)
}
}).padding(.horizontal, 12)
}
.navigationBarTitle(kMyToyBoxText)
.navigationBarItems(trailing: Button(action: signoutTapped, label: {
Image(systemName: "person.circle")
Text("Logout")
}))
}
}
}
struct SeriesInfo: View {
let series: Series
var body: some View {
NavigationLink(
destination: DetailView(series: series.seriesName)) {
VStack(alignment: .leading, spacing: 4) {
KFImage(URL(string: series.imageFirebaseURLString))
.onSuccess { r in
}
.onFailure { error in
}
.placeholder {
// Placeholder while downloading.
kMyToyBoxLogoImage
.resizable()
.font(.largeTitle)
.opacity(0.3)
.scaledToFit()
.cornerRadius(22)
}
.cancelOnDisappear(true) // cancel if scrolled past
.resizable()
.scaledToFit()
.cornerRadius(22)
Text(series.seriesName)
.font(.system(size: 10, weight: .semibold))
.padding(.top, 4)
// convert to strings to avoid commas
Text("\(String(series.fromYear)) - \(String(series.toYear))")
.font(.system(size: 9, weight: .regular))
Spacer()
}
}
}
}
Images
My Series class (also tried as a struct, but it alter the behavior)
// series class
class Series: Identifiable, ObservableObject {
var id = UUID()
let uniqueId: String
let recordName: String
let seriesName: String
let fromYear: Int
let toYear: Int
let isMyToyBoxActive: Bool
let logoImageName: String
// generated data and needs to be published
#Published var imageFirebaseURLString: String
init (uniqueId: String, recordName: String, seriesName: String, fromYear: Int, toYear: Int, isMyToyBoxActive: Bool, logoImageName: String, imageFirebaseURLString: String = "") {
self.uniqueId = uniqueId
self.recordName = recordName
self.seriesName = seriesName
self.fromYear = fromYear
self.toYear = toYear
self.isMyToyBoxActive = isMyToyBoxActive
self.logoImageName = logoImageName
self.imageFirebaseURLString = imageFirebaseURLString
}
}
extension Series: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self).hashValue)
}
}
extension Series: Equatable {
public static func ==(lhs: Series, rhs: Series) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
}
ModelView code - this code functions as expected to populate the view except the photo data from the downloadURL call
// Series listener and data retriever
class SeriesList: ObservableObject {
#Published var seriesArray = [Series]()
init() {
//... code removed for getting Series file data from Firebase
let series: Series = self.getSeriesFromString(stringSeries: $0)
self.seriesArray.append(series)
// set the image
let storageRefImage = storage.reference().child(kSeriesImageRoot)
let seriesImageRef = storageRefImage.child(series.logoImageName)
// local image
seriesImageRef.downloadURL { url, error in
if let error = error {
}
else
{
series.imageFirebaseURLString = url!.absoluteString // <-- code to update the image URL
}
}
}
}
Make it observed in info view as well
struct SeriesInfo: View {
#ObservedObject var series: Series // << here !!