Getting the current index in TabView (PageTabViewStyle) - swiftui

I can't added gif. I uploaded YouTube. Link here.
When Tabview is swipe, I want to show that image in imageView below.
Note: My code didn't work when I tried it with the model. That's why I edited my question.
MainView
struct MainView: View {
#State var selected: Movie = Movie(title: "", description: "", poster: "", thumbImage: [], video: "", positiveRate: 0, negativeRate: 0)
var body: some View {
ZStack {
Image(selected.poster)
.resizable()
.overlay(Blur().edgesIgnoringSafeArea(.all))
VStack {
TabView(selection: $selected) {
ForEach(movieList, id: \.id) { item in
MovieCell(movie: item)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
}
}
}
}
Model
struct Movie: Identifiable, Hashable {
var id = UUID()
var title: String
var description: String
var poster: String
var thumbImage: [String]
var video: String
var positiveRate: Int
var negativeRate: Int
}
Example Data
let movieList = [
Movie(title: "Minyonlar 2", description: "In the heart of the 1970s, amidst a flurry of feathered hair and flared jeans, Gru (Steve Carell) is growing up in the suburbs. A fanboy of a supervillain supergroup known as the Vicious 6, Gru hatches a plan to become evil enough to join them. Luckily, he gets some mayhem-making back-up from his loyal followers, the Minions. Together, Kevin, Stuart, Bob, and Otto - a new Minion sporting braces and a desperate need to please - deploy their skills as they and Gru build their first lair, experiment with their first weapons, and pull off their first missions. When the Vicious 6 oust their leader, legendary fighter Wild Knuckles (Alan Arkin), Gru interviews to become their newest member. It doesn't go well (to say the least), and only gets worse after Gru outsmarts them and suddenly finds himself the mortal enemy of the apex of evil. On the run, Gru will turn to an unlikely source for guidance, Wild Knuckles, and discover that even bad guys need a little help from their friends. Written by Universal Pictures", poster: "2", thumbImage: ["minions/m1","minions/m2","minions/m3"], video: Bundle.main.path(forResource: "minyonlar", ofType: "mp4")!, positiveRate: 150, negativeRate: 5, categoryType: .animation),
Movie(title: "Hiçkimse", description: "Emmy winner Bob Odenkirk (Better Call Saul, The Post, Nebraska) stars as Hutch Mansell, an underestimated and overlooked dad and husband, taking life's indignities on the chin and never pushing back. A nobody. When two thieves break into his suburban home one night, Hutch declines to defend himself or his family, hoping to prevent serious violence. His teenage son, Blake (Gage Munroe, The Shack), is disappointed in him and his wife, Becca (Connie Nielsen, Wonder Woman), seems to pull only further away. The aftermath of the incident strikes a match to Hutch's long-simmering rage, triggering dormant instincts and propelling him on a brutal path that will surface dark secrets and lethal skills. In a barrage of fists, gunfire and squealing tires, Hutch must save his family from a dangerous adversary (famed Russian actor Aleksey Serebryakov, Amazon's McMafia)-and ensure that he will never be underestimated as a nobody again. Written by Universal Pictures", poster: "1", thumbImage: ["hickimse/h1","hickimse/h2","hickimse/h3"], video: Bundle.main.path(forResource: "hickimse", ofType: "mp4")!, positiveRate: 100, negativeRate: 250, categoryType: .action),
Movie(title: "Six Minutes of Midnight", description: "Summer 1939. Influential families in Nazi Germany have sent their daughters to a finishing school in an English seaside town to learn the language and be ambassadors for a future looking National Socialist. A teacher there sees what is coming and is trying to raise the alarm. But the authorities believe he is the problem. Written by Andy Evans", poster: "3", thumbImage: ["sixminutes/s1"], video: Bundle.main.path(forResource: "sixMinutes", ofType: "mp4")!, positiveRate: 80, negativeRate: 60, categoryType: .war)
]

Just specify selection in image, like
Image(selected) // << here !1
.resizable()
.frame(width: 150, height: 200)
Tested on replicated code with Xcode 12.4 / iOS 14.4

You need to add tag to your TabView items. Note that these tags must be of type Movie - the same type as the selected property.
struct MainView: View {
#State var selected: Movie = movieList[0] // set the selection to the first item
var body: some View {
ZStack {
Image(selected.poster)
.resizable()
.overlay(Blur().edgesIgnoringSafeArea(.all))
VStack {
TabView(selection: $selected) {
ForEach(movieList, id: \.id) { item in
MovieCell(movie: item)
.tag(item) // `tag` items here
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
}
}
}
}

Related

Lingering popover causes a problem with alerts

The code below has 2 buttons, one for an alert, and one for a popup. The problem is that if you bring up the popup, then click the alert, you don't see an alert. Even after "clearing" the popup, you will never see an alert. However, if you bring up the popup, and clear it, then click the alert, that works fine. This is all on an ipad simulator as that is where a popup is sized according to content (so it can share the UI with many elements).
Of course, this is a simplified example. In my real code the button that brings up the alert is nowhere near the one for bringing up the popup (both UI-wise, and code-wise).
It's like i need some sort of global function that says something like "clear all popups" or something? Or maybe use something other than popover? I don't even know if this is a "bug" or a "feature"..
struct ContentView: View {
#State private var showPopover = false
#State private var showingAlert = false
var body: some View {
VStack {
Spacer()
Button {
showingAlert = true
} label: {
Text("Click to show alert")
}
.alert("Alerted!", isPresented: $showingAlert) {
Button("OK", role: .cancel) {}
}.padding()
Spacer()
Button("Click to show popover", action: {
self.showPopover = true
})
.popover(isPresented: $showPopover) {
Text("Popup Text")
}
Spacer()
}
}
}
I also see these messages in the console while all this is going on.
2022-04-06 12:43:28.494902-0700 TestBee[39557:16837171] [UIFocus] Failed to update focus with context <UIFocusUpdateContext: 0x600001440b40: previouslyFocusedItem=(null), nextFocusedItem=(null), focusHeading=None>. No additional info available.

Adding a URL within a Text object in Swift

I am battling with layout or padding or something similar in SwiftUI.
What I want is a view containing a sentence that reads "A website named StackOverflow exists for peer help" where StackOverflow is a URL that the user may tap and navigate to the StackOverflow website. The code below works however ...... the button with the link appears out of line. I want it to read like a sentence without line breaks or padding etc.
All ideas welcome, but it is not as simple as embedding in an HStack.
var body: some View {
Form {
Section(header: Text("something)")) {
let urlStackOverflow: URL = URL(string: "https://www.stackoverflow.com“)!
Text("A website named")
Button(action: { UIApplication.shared.open(urlStackOverflow) }, label: {
Text("StackOverflow“).bold()
})
Text("exists for peer help“)
}}}
Here's a solution with a single Button and multiple Texts added together as views.
struct ContentView: View {
let urlStackOverflow = URL(string: "https://www.stackoverflow.com")!
var body: some View {
Form {
Section(header: Text("something")) {
Button(action: { UIApplication.shared.open(self.urlStackOverflow) }) {
Text("A website named")
+ Text(" StackOverflow ")
.bold()
.foregroundColor(Color.blue)
+ Text("exists for peer help.")
}
.foregroundColor(Color.black)
}
}
}
}
Limitations:
The entire text would be clickable.
Multiple links are not supported.

Finding the accessibility(identifier:) of DatePicker() embedded in a Form in SwiftUI using an XCUIElementQuery

I have a SwiftUI view that has a Form that contains a DatePicker:
struct GettingUpTimeSettingView: View {
#State private var viewModel = GettingUpTimeSettingViewModel()
var body: some View {
VStack {
Form {
Spacer()
Text(viewModel.questionString)
.accessibility(identifier: "Question")
Spacer()
DatePicker("Alarm Time",
selection: $viewModel.gettingUpTime,
displayedComponents: .hourAndMinute)
.accessibility(identifier: "Time")
.animation(.easeInOut)
Spacer()
Text(viewModel.explanationString)
.accessibility(identifier: "Explanation")
}
}
}
}
And an XCTestCase class for UI Testing:
class SleepyGPIntroUITests: XCTestCase {
private var app: XCUIApplication!
override func setUp() {
continueAfterFailure = false
app = XCUIApplication()
app.launch()
greeting = app.staticTexts["Greeting"]
}
override func tearDown() {
app = nil
}
func test_InitialScreen_ChangesTo_GettingUpScreen_Automatically() {
//Given
let questionText = "What time do you want to get up?"
let explanationText = "This should be the same time every day, including at weekends and on days off. Our best chance of great sleep comes when we have a regular routine."
let timeText = "7:00am"
let question = app.staticTexts["Question"]
let explanation = app.staticTexts["Explanation"]
let time = app.staticTexts["Time"]
//When
_ = time.waitForExistence(timeout: 2)
//Then
XCTAssertFalse(greeting.exists)
XCTAssertEqual(question.label, questionText, "Should show question at top of view")
XCTAssertEqual(explanation.label, explanationText, "Should show explanation in view")
XCTAssertEqual(time.label, timeText, "Should show the correct default time")
}
When I run the test, it fails and gives me this message:
Failed to get matching snapshot: No matches found for Elements matching predicate '"Time" IN identifiers' from input {(
StaticText, identifier: 'Question', label: 'What time do you want to get up?',
StaticText, identifier: 'Explanation', label: 'This should be the same time every day, including at weekends and on days off. Our best chance of great sleep comes when we have a regular routine.'
)}
I'm not sure whether this is down to the DatePicker() being contained in a Form, or whether it's because I'm not using the correct XCUIElementQuery to find it?
When I move the DatePicker outside the Form, I can find it, but only by using its label, and not its accessibility identifier.
The accessibility identifiers are working fine for the Text objects.
I can also find it using
app.datePickers.firstMatch
when it's outside the form, but not when it's contained within it.
I've found this answer that describes some odd behaviour when SwiftUI objects are contained in forms, but still haven't managed to solve my problem.
Many thanks.
tl:dr Can't get XCUIElementQuery to return DatePicker element when UITesting if the DatePicker is contained in a SwiftUI Form.
I've found the answer, for anyone that's interested.
First of all, a piece of basic advice, if you're struggling to find out how to access an element when UI testing in XCode, just use the record function and access the element manually.
That's what I did, and it showed me that before a DatePicker() is tapped on in SwiftUI, it actually shows as a button, so to access it in the example above I used this code:
let alarmTimeButton = app.tables.buttons["Time"]

List not scrolling (in SwiftUI -- to disambiguate from other questions)

Using SwiftUI, I have a List of items retrieved from CoreData. Everything I read makes it look like the list should be something I can scroll normally, but in both the simulator and running on an iPad, the List doesn't scroll.
Clearly there is some piece missing, but I can't seem to find what it is. The list populates correctly, but it simply won't scroll.
struct PeopleList : View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(
entity: Person.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \Person.lastName, ascending: true),
NSSortDescriptor(keyPath: \Person.firstName, ascending: true)
]
) var people: FetchedResults<Person>
var body: some View {
ZStack(alignment: .topLeading) {
List(self.people, id: \.self) { person in
Text(person.descriptionForList())
}
}
.frame(width: 400, height: 200)
.modifier(RoundedEdge(width: 4, color: Color(red: 0.6, green: 0.6, blue: 0.6), cornerRadius: 10))
.padding(10)
}
}
Things that didn't change anything:
Removing the ZStack { }
Replacing the List with a simple List(0...100, id: \.self) { item in Text("hey \(item)") } -- still won't scroll.
Adding a .frame() to the List() itself.
(added) removing the .frame .modifier and .padding all accomplished nothing.
This is not about (the following topics have Q&A's addressing them, but not this issue):
programmatic scrolling
disabling scrolling
scroll indicators
ScrollView
UIKit
How can I track down what might be preventing the List() from scrolling?
Turns out, the issue wasn't with anything in the struct that was posed above; it all had to do with the View where that struct was being used. Specifically, when a Color() -- even of .opacity(0) -- was in a ZStack sitting "above" the List(), the latter stops scrolling. Displays fine, but just won't scroll.
This question describes the same thing happening, albeit in somewhat different circumstances.
I'll leave this question up, since somebody else may be at the same place I was, "why isn't my List() scrolling?", rather than "why isn't my List() in a ZStack scrolling?" Hopefully a version of Swift later than 13 will fix this behavior!

How do I get my UIView to correctly size for its content in SwiftUI?

I want to use the awesome MultiSegmentPicker written by Yonat Sharon in my SwiftUI View.
https://github.com/yonat/MultiSelectSegmentedControl
However, I don't fully understand the interaction between the UIViewRepresentable View and my SwiftUI View. How do I get the host view controller to shrink its height down to the size of the segmented control?
Here's the debugger view of the demo page - notice the blue area around the top bar:
The demo code doesn't give a lot of insight into the issue, it's just a call to the UIViewRepresentable view. I've simplified it to just one example here:
struct MultiSegmentPickerX: View {
#State private var selectedSegmentIndexes: IndexSet = []
var body: some View {
VStack(alignment: .center) {
Spacer()
MultiSegmentPicker(
selectedSegmentIndexes: $selectedSegmentIndexes,
items: ["First", "Second", "Third", "Done"]
)
}
}
}
Notice I have a VStack with a Spacer() before the control.
The desired behavior for this example would be that the bar with "First", "Second", etc. would be snug against the bottom of the screen. Instead the Host Controller holds onto all that space...
Do I need to use Geometry reader to solve this issue and shrink the height down. Or do I have to adjust something in the UIViewRepresentable View?
Any insights into bridging UIKit and SwiftUI are always appreciated...
Is this an easy fix anyone?
These did not solve my issue:
UIViewRepresentable content not updating
How do I make SwiftUI UIViewRepresentable view hug its content?
How do I size a UITextView to its content?
Cannot test it now, just by thoughts, try with fixed size as below
MultiSegmentPicker(
selectedSegmentIndexes: $selectedSegmentIndexes,
items: ["First", "Second", "Third", "Done"]
).fixedSize() // << here !!