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.
Related
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.
Swift 4, macOS 10.13
I have an NSOutlineView and I'm trying to customize the look of each row when the user clicks it.
I have a view-based NSTableCellView subclass that I'm using for the cell. When I override backgroundStyle, the icon change works perfectly. But the text color is doing something weird.
Here's a video to demonstrate: http://d.pr/v/suTD8B
Here's my code:
class MenuItemCell: NSTableCellView{
#IBOutlet weak var label: NSTextField!
#IBOutlet weak var icon: NSImageView!
//Customizes the selected state of menu row
override var backgroundStyle: NSView.BackgroundStyle {
set{
if let rowView = self.superview as? NSTableRowView {
super.backgroundStyle = rowView.isSelected ? .dark : .light
}
if self.backgroundStyle == .dark {
label.textColor = NSColor.white
icon.image = NSImage(named: NSImage.Name(rawValue: "menu-project-selected"))
}else{
label.textColor = NSColor.menuTextColor
icon.image = NSImage(named: NSImage.Name(rawValue: "menu-project"))
}
}
get{ return super.backgroundStyle }
}
}
Any idea what's wrong? I've been searching for hours and can't figure it out.
It turns out that this would never work reliably while the NSOutlineView Highlight was set to Source List in the storyboard editor. It was really unreliable.
Once I changed it to Regular then it worked fine.
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?
I have a Monotouch 6.0.10 iPhone app using the 6.1 SDK, but targeted to iOS 4.0 and up, where I am trying unsuccessfully to force just one of the views to portrait orientation, using ShouldAutorotateToInterfaceOrientation. It's now deprecated I realize, but nevertheless necessary to support iOS4/iOS5 devices.
To try to isolate the problem I wrote a minimal test app. It is XIB-less and has a UITabBarController with one tab. The tab has a UINavigationController and the UINavigationController has a UIViewController (with a hello world button to click).
In AppDelegate I have:
tabController = new TabController();
window.RootViewController = tabController;
In the UITabBarController and in the UINavigationController I have:
public override bool ShouldAutorotateToInterfaceOrientation(UIInterfaceOrientation toInterfaceOrientation)
{
return true;
}
In the UIViewController I have:
public override bool ShouldAutorotateToInterfaceOrientation(UIInterfaceOrientation toInterfaceOrientation)
{
if ( toInterfaceOrientation == UIInterfaceOrientation.Portrait )
{
return true;
}
else
{
return false;
}
}
Well, on an iOS 6.1 device at least, those ShouldAutorotateToInterfaceOrientation's seem to be completely ignored. Breakpoints there don't get reached, and if I force them to return false in every case, rotations still happen.
My understanding is that ShouldAutomaticallyForwardRotationMethods defaults to true so that would not seem to offer a solution. Have combed the forums with no luck, except a suggestion from Glen Schmidt here: iOS 6 rotations: supportedInterfaceOrientations doesn´t work? but unfortunately I'm lost on how to translate that to MonoTouch:
QUOTE
If you want to replicate the pre-iOS 6 behaviour where all the views in the navigation stack / tab bar have to agree on an allowable set of orientations, put this in your subclass of UITabBarController or UINavigationController:
- (NSUInteger)supportedInterfaceOrientations
{
NSUInteger orientations = [super supportedInterfaceOrientations];
for (UIViewController *controller in self.viewControllers)
orientations = orientations & [controller supportedInterfaceOrientations];
return orientations;
}
UNQUOTE
I understand also that I can't even hope to solve it just for my iOS6 users via ShouldAutoRotate/SupportedInterfaceOrientations because this would cause iOS4/IOS5 rotations to fail.
Any suggestion much appreciated!
Bill.
Found a solution that works, after quite a bit of thrashing about. Somebody might be able to improve on this.
PROBLEM
My app, based on SDK 6.1, targets iOS4, iOS5 and iOS6 devices. It needs to allow all screens to rotate, except for one which must be fixed in portrait orientation. The app has a UITabBarController, most tabs have a UINavigationController with stacked views beneath.
SOLUTION
For iOS4/iOS5 users, the key is the deprecated ShouldAutorotateToInterfaceOrientation() method in the app's root view controller (tab bar controller in my case). This method is not called automatically in any other view controller, but of course the root view controller can invoke a method of the same name in the current view controller.
For iOS6 users, the key is ShouldAutorotate() / GetSupportedInterfaceOrientations() in the app's root view controller. Again, these methods are not called automatically in any other view controller but the root view controller can invoke methods of the same name in the current view controller.
For my simple test app described in the original post:
In AppDelegate/FinishedLaunching:
window.RootViewController = tabController;
In AppDelegate:
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations (UIApplication application, UIWindow forWindow)
{
return UIInterfaceOrientationMask.All;
}
In TabController:
public override bool ShouldAutorotateToInterfaceOrientation(UIInterfaceOrientation toInterfaceOrientation) // iOS4/iOS5 only
{
try
{
UINavigationController navController = (UINavigationController)SelectedViewController;
UIViewController targetController = navController.ViewControllers[0];
if ( targetController.Title == "Greetings")
{
if ( toInterfaceOrientation == UIInterfaceOrientation.Portrait )
{
return true;
}
else
{
return false;
}
}
}
catch
{
return true;
}
return true;
}
public override bool ShouldAutorotate() // iOS6+ only
{
return true;
}
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations() // iOS6+ only
{
try
{
UINavigationController navController = (UINavigationController)SelectedViewController;
UIViewController targetController = navController.ViewControllers[0];
return targetController.GetSupportedInterfaceOrientations();
}
catch
{
return UIInterfaceOrientationMask.All;
}
return UIInterfaceOrientationMask.All;
}
In the view controller that needs to be portrait:
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations() // iOS6+ only
{
return UIInterfaceOrientationMask.Portrait;
}
My actual app needs something slightly more elaborate but along the same lines. For example in the tabbarcontroller:
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations()
{
try
{
UINavigationController navController = (UINavigationController)SelectedViewController;
if ( navController.Title == "Tracking" ) // Are we on the Tracking tab?
{
// Yes. Looking for RedLaser's BarcodePickerController
UIViewController targetController = navController.ViewControllers[1]; // BarcodePicker would be second on nav stack
string controllerType = targetController.GetType().Name;
if ( controllerType == "BarcodePickerController" ) // Is this BarcodePicker?
{
return UIInterfaceOrientationMask.Portrait; // Yes, force portrait orientation
}
}
else
{
return UIInterfaceOrientationMask.All; // No, allow any orientation
}
}
catch
{
return UIInterfaceOrientationMask.All; // Not BarcodePicker, allow any orientation
}
return UIInterfaceOrientationMask.All;
}