Getting items in an array to link to another view - swift

I'm building a music player app in swift. I have the 'MainView' where the albums are displayed via an array and the AlbumView that will show a new window where a user can play the album. All I want to do is present 'AlbumView' when a user taps on an album in the 'MainView'. I'm thinking that the albumview would dynamically display data related to whatever album is selected instead of creating new views for each album. but I'm open to multiple views if it's easier. Learning swift as i do this. Here is the current code.
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 30) {
ForEach(Array(zip(mainViewModel.artistsOfTheWeek.indices, mainViewModel.artistsOfTheWeek)), id: \.0) { (index, artist) in
Button(action: {
// Present the AlbumView when the button is tapped
let albumView = AlbumView()
if let topViewController = *UIApplication.shared.windows.first?*.rootViewController {
let hostingController = UIHostingController(rootView: albumView)
hostingController.modalPresentationStyle = .fullScreen
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = hostingController
window.makeKeyAndVisible()
topViewController.present(hostingController, animated: true, completion: nil)
}
}
}) {
ArtistView(artist: artist)
.padding(.top, 10)
.padding(.leading, 4)
.opacity(self.animateArtists ? 1 : 0)
.scaleEffect(self.animateArtists ? 1 : 0.5)
.animation(.easeInOut(duration: 0.3).delay(0.1 * Double(index)), value: self.animateArtists)
}
}
}
}
I'm getting this error ''windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead'n in the starred line above. I've tried wrapping the items in a navigation link also but got other errors. The preview also crashes when I tap on album but wanted to get this warning removed to see if that solves it before troubleshooting that as I don't get any explanation for why it's crashing

Related

preselectedAssetIdentifiers for iOS 14

I have a function like:
func presentPhotoLibrary() {
let photoLibrary = PHPhotoLibrary.shared()
if PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized {
var config = PHPickerConfiguration(photoLibrary: photoLibrary)
config.selectionLimit = 0
let phPicker = PHPickerViewController(configuration: config)
phPicker.delegate = self
present(phPicker, animated: true)
}
}
I also have a collection view that the user can select images from.
When the picker is presented, I want the selected images from the collection view to be shown as selected in the photo library picker. This function is achievable by PHPickerConfiguration 's preselectedAssetIdentifiers for iOS 15. However, I'm trying to have the same behaviour for iOS 14.
Any ideas?

SwiftUI alert and confirmation dialog preferredColorScheme

I've noticed when setting the preferredColorScheme in my SwiftUI app the .alert and .confirmationDialog modifiers don't use the colorScheme. Also the launch screen doesn't use the colorScheme either. It seems to be using system defaults, is this normal behaviour?
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.preferredColorScheme(.light)
}
}
}
I was also unable to get better results using UIKit's overrideUserInterfaceStyle. The launch screen is still using system defaults, unsure how to solve that in SwiftUI without a launch screen. Also the code becomes pretty ugly and much more extensive.
guard let scene = scenes.first as? UIWindowScene else { return }
scene.keyWindow?.overrideUserInterfaceStyle = .light // .light .dark .unspecified
I couldn't figure out a pure SwiftUI way, but I found a simple solution. Use UIKit, then handle the changes in SwiftUI. I just needed to use .overrideUserInterfaceStyle. As for the Launch Screen in SwiftUI, add a Background color key to the dictionary. Then add a Color setfrom your assets and set the Background color key value to the name of the asset created as a String.
let scenes = UIApplication.shared.connectedScenes
guard let scene = scenes.first as? UIWindowScene else { return nil }
scene.keyWindow?.overrideUserInterfaceStyle = .light //.dark, .light, .unspecified

SwiftUI - disclosure group expanding behaviour - keep top item static

I'm trying to create a drop down menu using swift UI. I'm actually implementing this into a UIKit project.
The basic functionality I am going for should be that the user clicks on a label with a certain data unit on it and a list expands with other units of measure that can be selected.
Here is my SwiftUI code:
import SwiftUI
#available(iOS 14.0.0, *)
struct DataUnitDropDown: View {
var units = ["ltr", "usg", "impg"]
#State private var selectedDataUnit = 0
#State private var isExpanded = false
var body: some View {
VStack(alignment: .leading, spacing: 15) {
DisclosureGroup(units[selectedDataUnit], isExpanded: $isExpanded) {
VStack {
ForEach(0 ..< units.count, id: \.self) { index in
Text("\(units[index])")
.font(.title3)
.padding(.all)
.onTapGesture {
self.selectedDataUnit = index
withAnimation {
self.isExpanded.toggle()
}
}
}
}
}
}
}
}
So I have a couple of questions here. Firstly, ideally I wanted to place this into an existing horizontal UIStackView. However, the issue I have is that obviously once the dropdown expands, it increases the height of the stack, which is not what I want. Here is a screen shot of the component (see 'ltr'):
When expanded in the stackView:
So I reluctantly placed it in the main view adding some autolayout constraints:
if #available(iOS 14.0.0, *) {
let controller = UIHostingController(rootView: DataUnitDropDown())
controller.view.translatesAutoresizingMaskIntoConstraints = false
addSubview(controller.view)
controller.view.leadingAnchor.constraint(equalTo: refuelInfoView.actualUpliftVolume.trailingAnchor).isActive = true
controller.view.centerYAnchor.constraint(equalTo: refuelInfoView.actualUpliftVolume.centerYAnchor).isActive = true
}
But now when I expand, the whole menu shifts up:
Clearly not what I am looking for.
So I suppose my question is 2-fold:
1- Is there any way I can incorporate this element into an existing stack view without the stack increasing in height when the dropdown is expanded? (I guess not!)
2- If I have to add this to the main view rather than the stack, how can I stop the entire menu shifting up when it expands?
Update:
I have amended the code as follows:
if #available(iOS 14.0.0, *) {
let controller = UIHostingController(rootView: DataUnitDropDown())
controller.view.translatesAutoresizingMaskIntoConstraints = false
controller.view.clipsToBounds = false
controller.view.heightAnchor.constraint(equalToConstant: 22).isActive = true
controller.view.topAnchor.constraint(equalTo: topAnchor).isActive = true
stackSubviews = [inputField, controller.view]
}
Now, without the topAnchor constraint this works perfectly, but I do need this constraint. But I get an error due to the views. not being in the same hierarchy
I assume instead of
controller.view.centerYAnchor.constraint(equalTo:
refuelInfoView.actualUpliftVolume.centerYAnchor).isActive = true
you need
controller.view.topAnchor.constraint(equalTo:
refuelInfoView.actualUpliftVolume.bottomAnchor).isActive = true

Sharing Screenshot of SwiftUI view causes crash

I am grabbing a screenshot of a sub-view in my SwiftUI View to immediately pass to a share sheet in order to share the image.
The view is of a set of questions from a text array rendered as a stack of cards. I am trying to get a screenshot of the question and make it share-able along with a link to the app (testing with a link to angry birds).
I have been able to capture the screenshot using basically Asperi's answer to the below question:
How do I render a SwiftUI View that is not at the root hierarchy as a UIImage?
My share sheet launches, and I've been able to use the "Copy" feature to copy the image, so I know it's actually getting a screenshot, but whenever I click "Message" to send it to someone, or if I just leave the share sheet open, the app crashes.
The message says it's a memory issue, but doesn't give much description of the problem. Is there a good way to troubleshoot this sort of thing? I assume it must be something with how the screenshot is being saved in this case.
Here are my extensions of View and UIView to render the image:
extension UIView {
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
extension View {
func asImage() -> UIImage {
let controller = UIHostingController(rootView: self)
// locate far out of screen
controller.view.frame = CGRect(x: 0, y: CGFloat(Int.max), width: 1, height: 1)
UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view)
let size = controller.sizeThatFits(in: UIScreen.main.bounds.size)
controller.view.bounds = CGRect(origin: .zero, size: size)
controller.view.sizeToFit()
controller.view.backgroundColor = .clear
let image = controller.view.asImage()
controller.view.removeFromSuperview()
return image
}
}
Here's an abbreviated version of my view - the button is about halfway down, and should call the private function at the bottom that renders the image from the View/UIView extensions, and sets the "questionScreenShot" variable to the rendered image, which is then presented in the share sheet.
struct TopicPage: View {
var currentTopic: Topic
#State private var currentQuestions: [String]
#State private var showShareSheet = false
#State var questionScreenShot: UIImage? = nil
var body: some View {
GeometryReader { geometry in
Button(action: {
self.questionScreenShot = render()
if self.questionScreenShot != nil {
self.showShareSheet = true
} else {
print("Did not set screenshot")
}
}) {
Text("Share Question").bold()
}
.sheet(isPresented: $showShareSheet) {
ShareSheet(activityItems: [questionScreenShot!])
}
}
}
private func render() -> UIImage {
QuestionBox(currentQuestion: self.currentQuestions[0]).asImage()
}
}
I've found a solution that seems to be working here. I start the variable where the questionScreenShot gets stored as nil to start:
#State var questionScreenShot: UIImage? = nil
Then I just make sure to set it to 'render' when the view appears, which means it loads the UIImage so if the user clicks "Share Question" it will be ready to be loaded (I think there was an issue earlier where the UIImage wasn't getting loaded in time once the sharing was done).
It also sets that variable back to nil on disappear.
.onAppear {
self.currentQuestions = currentTopic.questions.shuffled()
self.featuredQuestion = currentQuestions.last!
self.questionScreenShot = render()
}
.onDisappear {
self.questionScreenShot = nil
self.featuredQuestion = nil
}

SpriteKit scene deformed after modal presentation swift

Could use some help troubleshooting an issue with a SpriteKit scene.
I have a scene that displays some coins in the main section of the app.
When I present a viewcontroller from the bottom I have no issue. Same for tab bar navigation, no issues.
Here is the view as it should always be displayed.
The issue comes only when I present a viewcontroller from the side.
When the new viewcontroller is dismissed, the scene works, but is distorted.
this is how it is displayed after a viewcontroller is displayed modally and later on dismissed.
EDIT: I forgot to mention that if I swipe vertically on the distorted scene, the distortion is fixed and all is good.
Here is some of the code in viewDidAppear of the viewcontroller.
Thanks for the help.
EDIT 2:
I just tested the app on a iPhone 5 using iOS 10 and the issue doesn't happen. Any chance this might be iOS 11 related?
func configureScene(_ completion: () -> Void) {
defer { completion() }
guard wScene == nil else { return }
let skView = SKView(frame: self.view.frame)
skView.isUserInteractionEnabled = false
skView.backgroundColor = .clear
wScene = WScene(size: view.frame.size)
wScene.backgroundColor = .clear
skView.presentScene(wScene)
view.insertSubview(skView, belowSubview: collectionView)
if let buttonsObstacle = doubleButton?.buttonsView {
let obstacleSize = CGSize.init(width: buttonsObstacle.frame.width, height: buttonsObstacle.frame.height)
obstacle = SKSpriteNode.init(color: .clear, size: obstacleSize)
guard let obstacle = obstacle else { return }
obstacle.name = WScene.obstacleNodeName
let convertedOrigin = view.convert(buttonsObstacle.center, from: buttonsObstacle.superview)
let skConvertedOrigin = skView.convert(convertedOrigin, to: wScene)
obstacle.position = skConvertedOrigin
obstacle.physicsBody = SKPhysicsBody(rectangleOf: obstacleSize)
obstacle.physicsBody?.allowsRotation = false
obstacle.physicsBody?.isDynamic = false
source.scrollHandler = { [weak self] (scrollView) in
guard let strongSelf = self else { return }
strongSelf.buttonsMoved(inView: skView, withScroll: scrollView)
}
wScene.addChild(obstacle)
presenter.loadData()
}
}
I solved my issue.
It was related to the new iOS 11 adjustedContentInset property.
My Coin SK scene was being moved by the scroll handler when the view appeared after a modal transition.
My solution is to disable the scrolling for the first 0.1 second after the view appears. In this way iOS 11 doesn't touch the coins anymore while users are able to scroll correctly because they interact with the view most of the time after at least 0.1 seconds.