I'm not sure I am approaching this correctly. I have a long rectangular box that I want to add -1.5 from the camera when the app starts up. But I want it to be stationary, like the ship that comes default in an ARKit project. But whenever I add it, the object stays relative (distance wise) to the camera. i.e - move towards it, it moves back, move back, it moves forward.
I though dropping an anchor on the scene would resolve this but I am still getting the same affect. Here is my code. Any help would be appreciated:
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
//sceneView.showsStatistics = true
// Create a new scene
let scene = SCNScene()//
// Set the scene to the view
sceneView.scene = scene
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
//configuration.planeDetection = .horizontal
// Run the view's session
sceneView.session.run(configuration)
print(#function, sceneView.session.currentFrame)
}
// MARK: - SCNSceneRendererDelegate
func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) {
print(#function, sceneView.session.currentFrame)
if !hasPortalAnchor {
//add anchor - this may take a second as the current frames are initially nil
if let currentFrame = sceneView.session.currentFrame {
var translation = matrix_identity_float4x4
translation.columns.3.z = -1.3
let transform = simd_mul(currentFrame.camera.transform, translation)
if (arrAnchors.count < 1) {
let portalAnchor = ARAnchor(transform: transform)
sceneView.session.add(anchor: portalAnchor)
arrAnchors.append(portalAnchor)
print(arrAnchors)
}
}
} else {
hasPortalAnchor = true
}
}
//this function gets called whenever we add an anchor to our scene
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
let portalScene = SCNScene(named: "art.scnassets/portal.scn")!
return portalScene.rootNode.childNode(withName: "portal", recursively: true)
}
Are you deliberately using the renderer(_:) function? If not then you can just use the following viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
let scene = SCNScene(named: "art.scnassets/portal.scn")!
sceneView.scene = scene
}
This will replace the default rocket that appears on startup, with your portal scene. (Note that the scene may move around if tracking hasn't been established. For instance if the light is low, or if you are in an environment without many features (a blank or repetitive wall for instance)).
Also it looks like you're not actually setting hasPortalAnchor to true? Is it being set somewhere else?
Related
I want to determine the background colour of an imageview when it is tapped and perform an action based on the colour. I cannot fathom the syntax for getting the information from the array in which the imageviews are stored. I have tried the below but it says the stated error. Any help would be appreciated. I am new and I'm sorry if I'm wasting peoples time, I'm learning by making my own app and its throwing up a lot of questions. Kind regards.
'Value of type [UIImageView] has no member backgroundColour.
#IBAction func onTapping() {
if squares.backgroundColor == UIColor.red {
// code
}
}
First you need to set userIteractionEnable on all your UIImageViews
Next change your method signature to:
#IBAction func onTapping(_ sender: UITapGestureRecognizer) {
Then you will need to know which image view you are tapping on by checking the sender view property:
#IBAction func onTapping(_ sender: UITapGestureRecognizer) {
let systemRed = UIView()
systemRed.backgroundColor = .systemRed
if sender.view?.backgroundColor == systemRed.backgroundColor {
print("systemRed")
} else {
print("systemYellow")
}
}
Like the title says, I'm trying to use an #EnvironmentObject that holds data to populate and update a List automatically in SwiftUI.
For context, I'm building an app that is supposed to show a list of locations. The location data will be shown in multiple places of my app and will be changing during use, so I thought an #EnvironmentObject would be perfect for holding the data. But, I'm having trouble feeding an #EnvironmentObject to populate a List() and having the list update as the #EnvironmentObject changes.
Below is the struct I created for the location data I want to display:
struct ListVenue : Identifiable {
var id = UUID()
var name: String
var formatted_address : String?
var website : String?
}
Below is my SceneDelegate.swift file, where I create the #EnvironmentObject and the class it references:
import UIKit
import SwiftUI
class venDataArray: ObservableObject {
var array : [ListVenue] = [ListVenue(name: "test_name")]
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
#EnvironmentObject var VDArray: venDataArray
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView().environmentObject(VDArray)
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
And, here is the my ContentView.swift file where I want to use the #EnvironmentObject above to populate and update a List() automatically:
import SwiftUI
struct ContentView: View {
#EnvironmentObject var VDArray: venDataArray
var body: some View {
VStack(alignment: .leading) {
Text("Location List")
// This is where im failing at having a list take in an #EnvironmentObject
List(VDArray) { ListVenue in
// vvv This is the view I want displayed for each item in the #EnvironmentObject vvv
VenueRowTest()
}
}
}
Can anyone show me how to alter my code so that I can display and update data in a list using an #EnvironmentObject??
Or is there a better way to implement a dynamic List?
Have a look at the following tutorials:
How to use environmentobject to share data
Apple - handling user inputs
First You need a #Published property wrapper for your code to work.
class VenDataArray: ObservableObject {
#Published var array : [ListVenue] = [ListVenue(name: "test_name")]
}
Than adjust yout Scene delegate
var window: UIWindow?
var vDArray = VenDataArray()
let contentView = ContentView().environmentObject(vDArray)
Note: I have adjusted the variables with lowerCamelCase acc. to the API design guidelines
I change title font and color like this:
let titleAttributes = [NSAttributedStringKey.font: UIFont(name: "HelveticaNeue-Bold", size: 25)!, NSAttributedStringKey.foregroundColor: UIColor.purple]
alert.setValue(titleString, forKey: "attributedTitle")
Before iOS13 this worked fine both for preferredStyle .alert and .actionSheet.
Now it only works for .alert and doesn't work for .actionSheet.
Someone please any help?
iOS 13 now embeds the UIAlertController title in a UIVisualEffectView, and only the title's alpha channel affects its appearance. If it is critical to control the exact color, I think you could try finding the subview class _UIInterfaceActionGroupHeaderScrollView, remove its child UIVisualEffectView, and then add your own UILabel back in. But no guarantees it will work or that it won't trigger a crash. You can use the allSubviews extension below and compare each to this:
let grpHeader = NSClassFromString("_UIInterfaceActionGroupHeaderScrollView")
Or if you just need to ensure that your title is visible based on your app's color scheme I had success with the following, which should be quite safe to use at least until iOS 14 is released.
let alertController = UIAlertController(....)
for subView in UIView.allSubviews(of: alertController.view) {
if let effectView = subView as? UIVisualEffectView {
if effectView.effect is UIVibrancyEffect {
if #available(iOS 13.0, *) {
// iOS 13.1 default blur style is UIBlurEffectStyleSystemVibrantBackgroundRegular which is NOT currently defined anywhere
// if your alert controller color is dark, set to .systemMaterialDark
// if your alert controller color is light, set to .systemMaterialLight
effectView.effect = UIVibrancyEffect(blurEffect: UIBlurEffect(style: UIBlurEffect.Style.systemMaterialDark), style: .secondaryLabel)
}
break
}
}
}
// You will need this UIView category to get an array of all subviews,
// as the view heirarchy is complex. In iOS13 it is:
// UIAlertController.UIView ->
// _UIAlertControllerInterfaceActionGroupView ->
// UIView
// _UIInterfaceActionGroupHeaderScrollView
// ** UIVisualEffectView **
// _UIInterfaceActionRepresentationsSequenceView
// _UIDimmingKnockoutBackdropView ]
// ** all your button actions **
extension UIView {
class func allSubviews<T : UIView>(of view: UIView) -> [T] {
var subviews = [T]()
for subview in view.subviews {
subviews += allSubviews(of: subview) as [T]
if let subview = subview as? T {
subviews.append(subview)
}
}
return subviews
}
}
Note: I am using iOS11s native mapview annotation clustering.
In a situation where annotations are still clustered at max zoom, in what manner can we show a callout?
I'm showing a pop-over type view to display a list of annotations at the cluster, but calling selectAnnotation isn't enough to show a callout for an annotation that is "clustered".
"Something" is selected, but no callout is shown. By something, I just mean that my didDeselect view method is called after I touch the mapview.
I ran through the same problem. Seems that they didn't think carefully in that case. You must select the MKClusterAnnotation instead the MKAnnotation that is clustered but it's not simple to get there.
on iOS11 there's a property on MKAnnotationView called cluster that as the documentation states is:
If non-nil this is the annotation view this view is clustered into.
So in my MKAnnotationView subclass I override the setSelected method and with a weak reference to the mapView you must select the cluster one:
//You have weak reference to mapView
weak var mapView: MKMapView?
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if #available(iOS 11.0, *) {
if selected, let cluster = cluster {
// Deselect the current annotation (Maybe this step is not required, didn't check it)
if let annotation = annotation {
mapView?.deselectAnnotation(annotation, animated: false)
}
// Select the cluster annotation
if let clusterAnnotation = cluster.annotation {
mapView?.selectAnnotation(clusterAnnotation, animated: true)
}
}
}
}
Its actually quite simple. The map view doesn't bother showing a callout if the assigned MKMarkerAnnotationView is not set to show callouts through .canShowCallout and also if there are no accessories on the view (that's the important one). If those two conditions are not met then the map can show the title and subtitle on the pin itself. So, here is all you have to do:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if annotation is MyAnnotationConformingClass {
let annotation = annotation as! MKAnnotation
let view = MKAannotationView(annotation: annotation, reuseIdentifier: "pinReUserId")
view.canShowCallout = true
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
return view
}
if annotation is MKClusterAnnotation {
let annotation = annotation as! MKClusterAnnotation
let view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "ClusterResuseId")
view.canShowCallout = true
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
return view
}
return nil
}
By giving the cluster's MKMarkerAnnotationView an accessory and also allowing show callouts, the callout will then be shown. If you remember with older SDKs, the map would not show a callout if you had no title and subtitle set.
I am using a UIPageViewController with transitionStyle UIPageViewControllerTransitionStyleScroll and navigationOrientation UIPageViewControllerNavigationOrientationVertical
I also have a UIPanGestureRecognizer on the view and I want to disable page scrolling when the pan gesture is active.
I am trying to set the following when the gesture begins:
pageViewController.view.userInteractionEnabled = NO;
This seems to have no effect, or it appears to work sporadically.
The only other way I have found to do it (which works) is to set the UIPageViewController dataSource to nil while the pan gesture is running, however this causes a huge delay when resetting the dataSource.
UIPageViewController uses some UIScrollView object to handle scrolling (at least for transitionStyle UIPageViewControllerTransitionStyleScroll). You can iterate by controller's subviews pageViewController.view.subviews to get it. Now, you can easly enable/disable scrolling:
- (void)setScrollEnabled:(BOOL)enabled forPageViewController:(UIPageViewController*)pageViewController
{
for (UIView *view in pageViewController.view.subviews) {
if ([view isKindOfClass:UIScrollView.class]) {
UIScrollView *scrollView = (UIScrollView *)view;
[scrollView setScrollEnabled:enabled];
return;
}
}
}
For those who are using swift instead of objective-c, here is Squikend's solution transposed.
func findScrollView(#enabled : Bool) {
for view in self.view.subviews {
if view is UIScrollView {
let scrollView = view as UIScrollView
scrollView.scrollEnabled = enabled;
} else {
println("UIScrollView does not exist on this View")
}
}
}
Everyone is complicating this very very much.
This is the whole function you need to disable or enable the scroll.
func enableScroll(_ enable: Bool) {
dataSource = enable ? self : nil
}
Swift 4.2 Version of the answer
func findScrollView(enabled: Bool) {
for view in self.view.subviews {
if view is UIScrollView {
let scrollView = view as! UIScrollView
scrollView.isScrollEnabled = enabled
} else {
print("UIScrollView does not exist on this View")
}
}
}
then yourpagecontorller.findScrollView(enabled: false)
You may also disable gesture recognizers.
for (UIGestureRecognizer *recognizer in pageViewController.gestureRecognizers)
{
recognizer.enabled = NO;
}
Unlike the popular answer, which relies on existing of the inner UIScrollView, this one uses public gestureRecognizers array. The underlying scroll view may not exist if you use book-style paging.