I'm working with the iOS Designer and I have a storyboard with different views on it (navigation controller, ...). On nearly each view there is a red exclamation mark on the right side at the bottom. I get errors like
System.NullReferenceException
Object reference not set to an instance of an object
If I go to the corresponding code line:
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations ()
{
return TopViewController.GetSupportedInterfaceOrientations();
}
This is part of my CustomNavigationController. If I set the class name on the iOS Designer I get the error. What do I have to add here? The constructor looks like the following:
public CustomNavigationController (IntPtr handle) : base (handle)
{
}
Do I have to instantiate it in the AppDelegate?
On another view controller I get a similar Exception (with a different stack) and it points me to this line:
this.NavigationController.NavigationBar.TintColor = UIColor.FromRGB (21, 66, 139);
The app seems to work fine on simulator and on device though.
How do I resolve this issue?
EDIT:
I now removed the code in my view controller:
this.NavigationController.NavigationBar.TintColor = UIColor.FromRGB (21, 66, 139);
As replacement I moved this code to the Application Delegate FinishedLaunching():
UINavigationBar.Appearance.TintColor = UIColor.FromRGB (21, 66, 139);
The result is that now I don't have the exclamation mark for this view controller and the color is as desired. But the preview in the iOS Designer doesn't show me the correct color anymore.
If I comment out GetSupportedInterfaceOrientations() in my CustomNavigationController than the other exclamation mark has been removed. I thought I need this function for defining the orientation of each view controller. A quick look showed that it seems to work.
There are restriction on what the design time can support. You should replace any dynamic data, for example data loaded from a service, with dummy data when on design mode. The same applies to runtime objects like TopViewController which is only available at runtime.
if (Site != null && Site.DesignMode)
{
// use dummy data for designer surface
}
else
{
// use live data
}
This document explains it in more detail:
http://developer.xamarin.com/guides/ios/user_interface/designer/ios_designable_controls_overview/
Related
I'm trying to use a UISplitViewController where the secondary controller should expose a "close" function (via a button or button bar item) whenever the UISplitViewController is in side-by-side mode, but should hide the function at other times. I tried putting this in the secondary view controller:
override func viewWillAppear(_ animated: Bool) {
if splitViewController!.primaryHidden {
// hide the "close" UI artifact
} else {
// show the "close" UI artifact
}
}
This correctly sets the visibility of the "close" function when the secondary view is first displayed, but if the UISplitViewController switches between expanded and collapsed (say, by rotating an iPhone 6s Plus), then this function is not called again (which makes sense, as the secondary controller remains visible). Consequently, the "close" function remains in its initial state--hidden or shown--even as the UISplitViewController changes mode.
How can I get the "close" function to hide or show in reaction to changes in the mode of the UISplitViewController?
There is the UIViewControllerShowDetailTargetDidChangeNotification notification for that:
// Sometimes view controllers that are using showViewController:sender and
// showDetailViewController:sender: will need to know when the split view
// controller environment above it has changed. This notification will be
// posted when that happens (for example, when a split view controller is
// collapsing or expanding). The NSNotification's object will be the view
// controller that caused the change.
UIKIT_EXTERN NSNotificationName const UIViewControllerShowDetailTargetDidChangeNotification NS_AVAILABLE_IOS(8_0);
Use as follows
- (void)viewDidLoad{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(showDetailTargetDidChange:) name:UIViewControllerShowDetailTargetDidChangeNotification object:self.splitViewController];
}
- (void)showDetailTargetDidChange:(NSNotification *)notification{
// changed from collapsed to expanded or vice versa
}
This Apple sample demonstrates how the table cell accessory changes from a disclosure indicator in portrait (denoting that a push will happen) to it being removed when changing to landscape split view:
https://developer.apple.com/library/archive/samplecode/AdaptivePhotos/Introduction/Intro.html
Note on iOS 13 beta, use addObserver with object nil because there currently is a bug they send the notification using the wrong object. They use their new UISplitViewControllerPanelImpl from the internal class cluster instead of the UISplitViewController object.
http://www.openradar.appspot.com/radar?id=4956722791710720
For future reference:
What about using the UISplitViewControllerDelegate??
It has a method called
splitViewController:willChangeToDisplayMode:
that should do exactly what you where looking for.
Documentation here
Okay, I found a simple solution. I was making a novice mistake. The trick is to override viewWillLayoutSubviews() instead of viewWillAppear(animated:). Then everything works as I want. It seems that viewWillLayoutSubviews() is called (sometimes more than once) every time the containing UISplitViewController changes its display mode, and that's exactly what I need to respond to. The only gotcha is that splitViewController might be nil on some of those calls, so it needs to be implemented like this:
override func viewWillAppear(_ animated: Bool) {
if let svc = splitViewController {
if svc.primaryHidden {
// hide the "close" UI artifact
} else {
// show the "close" UI artifact
}
}
}
As part of my stumbling around to find a solution, I tried overriding traitCollectionDidChange(previousTraitCollection:). (I tried this because I wanted to react to device rotations.) At first I thought I was onto something, because this function also get called whenever the device rotates. Interestingly (and, frustratingly) I found that my view's splitViewController property was nil when this function is called. It seems odd that this should be so, since neither viewDidDisappear(animated:) nor viewWillAppear(animated:) is called when the UISplitViewController reconfigures itself. But why it should be nil is, I suppose, a question for another day.
As far as I have worked in xamarin declaring global variables in view controller is very important or else it can be garbage collected when it comes to Binding and all concerned.
likewise for nsnotification we have to have a global reference of nsobject type and remove the nsobject when we don't need nsnotification.
there are no practical documentation available and making native iOS developers to get frustrated with xamarin.ios
Suggesting few things like this would be a good help for any ios developer who becomes xamarin.ios developer
In Every ViewControllers Write following Code:
public override void ViewDidDisappear (bool animated)
{
//Executed when we navigate to other view, moving this ViewController in Navigation stack
//1. UnSubscribe All Events Here (Note: These must be SubScribed back in ViewWillAppear)
btnLogin.TouchupInside -= OnLoginClicked
//2. Remove Any TapGuestures (Note: These must be added back in ViewWillAppear)
if (singleTapGuesture != null)
{
scrollView.RemoveGestureRecognizer(singleTapGuesture);
singleTapGuesture.Dispose();
singleTapGuesture = null;
}
//3. Remove any NSNotifications (Note: These must be added back in ViewWillAppear)
//4. Clear anything here, which again initializes in ViewWillAppear
if (ParentViewController == null) {
//This section will be executed when ViewController is Removed From Stack
//1. Clear everything here
//2. Clear all Lists or other such objects
//3. Call Following Method which will clear all UI Components
ReleaseDesignerOutlets ();
}
base.ViewDidDisappear (animated);
}
This will clear out all unwanted object when ViewController is Navigated or Removed from the stack.
For UITableViews, use WeakDataSource & WeakDelegate.
There can be many more based on following Links:
Ref 1
Ref 2
I have a delared the following
public class ViewBase : ContentView
{
//...
}
When I use that in XAML
<local:ViewBase xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyForms;"
x:Class="MyForms.Finder"
BackgroundColor="Color.Yellow">
<!-- ... -->
</local:ViewBase>
When I use a CustomRenderer for that and even (as below) do nothing in it, the BackgroundColor from above is not set. When I don't define the following lines the background is yellow as expected.
[assembly: ExportRenderer(typeof(ViewBase), typeof(ViewRendererBase))]
namespace MyiOS
{
public class ViewRendererBase : ViewRenderer
{
}
}
BackgroundColor is a Property of ViewRenderer. I had a look into the code, it seems as Control is no set (I don't call SetNativeControl) it can't set Control.BackgroundColor to a value. But why does this happen? My guess is that something is wrong with the inheritance of ViewRenderer, as the default behaviour uses something different on ContentView!?
Not sure whether this is a bug in our docs [1] or a bug in the iOS ViewRenderer code [2] for the SetBackgroundColor method. So there are a couple of ways to workaround this. One is to have your custom renderer inherit from VisualElementRenderer<T> instead, e.g.:
public class ViewBaseRenderer : VisualElementRenderer<ContentView>
{
//...
}
When checking on the default renderer type in iOS code with:
var contentRenderer = Platform.CreateRenderer(new ContentView())
var rendererType = contentRenderer.GetType();
rendererType is a VisualElementRenderer<T>, so this would seem to be the default renderer that is used by Forms, so it would seem to be a bug in the docs.
Another "workaround" would be to use the ViewRenderer but override the SetBackgroundColor method:
public class ViewBaseRenderer : ViewRenderer
{
protected override void SetBackgroundColor(Color color)
{
base.SetBackgroundColor(color);
if (NativeView == null)
return;
if (color != Color.Default)
NativeView.BackgroundColor = color.ToUIColor();
}
I have brought this up with the Xamarin Forms team to determine whether it is a bug in the docs or a bug in the Forms iOS Platform code for the ViewRenderer. If you look at the forms source code I linked [2] you will see that if Control is null, which it is in this case, the background color is never set. By overriding and adding the code to set the background color for NativeView then you can workaround this possible bug.
So apparently it seems the docs are in error. If you use ViewRenderer you have to set the Control yourself. Other renderers that inherit from ViewRenderer, like LabelRender, set the Control already, but ViewRenderer does not, so you would have to call SetNativeControl() in the OnElementChanged override to create and set a native control. See this forum post [3]. Personally, I think that one should inherit from the renderer that is used by default, which in this case is a VisualElementRenderer<T>
[1] https://developer.xamarin.com/guides/xamarin-forms/custom-renderer/renderers/#Layouts
[2] https://github.com/xamarin/Xamarin.Forms/blob/74cb5c4a97dcb123eb471f6b1dffa1267d0305aa/Xamarin.Forms.Platform.iOS/ViewRenderer.cs#L99
[3] https://forums.xamarin.com/discussion/comment/180839#Comment_180839
I have a iOS + Watch app with a UIPicker, and I keep getting logs related to it that I can't figure out related to the Watch app:
[default] -[SPRemoteInterface handlePlistDictionary:fromIdentifier:]:2977: ComF:->Plugin method .pickerFocus is not implemented by the controller (null)
[default] -[SPRemoteInterface handlePlistDictionary:fromIdentifier:]:2977: ComF:->Plugin method .pickerClearFocus is not implemented by the controller (null)
I checked the documentation and the closest thing I could find was just the pickerDidFocus() method, which seems to be talking about general problem area I'm having, but no clear implementation ideas for specifically .pickerClearFocus and .pickerFocus.
Same with ".pickerSettle" ...
"I'm having the same problem. Looks like a bug of the iOS or XCode, because the public API does not incude a method ".pickerSettle", and the existing method "pickerDidSettle" is actualy called just fine. Everything seems to be working as expected."
from:
https://forums.developer.apple.com/thread/68971
I did found a solution for my case (WKInterfaceButton). The button reference outlet and the button sent action outlet must be in the same class. The message is shown when selector outlet references to the view and the action references the view controller.
Cell :
class aCellView: NSObject {
var delegate: WKInterfaceController? = nil
// The action outlet
#IBAction func doSomeThing() {
self.delegate!.doSomeThing()
}
}
Controller :
class HomeInterfaceController: WKInterfaceController {
// ... where you implement your cell
cell.delegate = self
// ... where you implement your cell
}
This seems like it should have a simple answer, and probably does, but it's proving harder to find than I expected. As a specific example, let's say that I'm programming a chess game.
It seems like this is something I should be able to do just using CoreGraphics. It seems like using OpenGL or SpriteKit shouldn't be necessary.
I want to have a Board class that models the state of the board. Where should I declare my Board object? My impression is that it should be in the ViewController.
I want to have a view (actually a subview) that displays the current state of the board (by overloading drawRect). It should do this at the beginning, and should be updated when players make moves. How does the view access the data model to display the board state? Does giving the view a reference to the data violate MVC? If not, how would the reference be passed to the view? (I seem to just find lots of links about passing data between two ViewControllers.)
Should it instead be the ViewController "pushing" the data to the view whenever it needs to be drawn? My understanding, though, is that drawRect should not be called directly, and that instead setNeedsDisplay should be called, which will indirectly result in drawRect being called. This being the case, it's hard to see how the data would be passed.
Your code; your design decision. Nothing to comment on here.
You should have your model declaration in ViewController. True. That is how MVC works.
Having a reference of the data in a UIView DOES break MVC. Your view instance will not be independent anymore. Decoupling view and model is one of the main points of MVC and you are probably breaking it with this design.
What can you do about it?
Extending #Paulw11's comment, in your view controller you can declare a method that looks something like this :
func movePiece(somePiece : Piece, toSquare : Square) {
let pieceID = somePiece.id //I am just assuming the models structures
let pieceImageView = self.pieceImageViewFromID(id) //Assume that this method returns the reference of the image view. Assume that I am just working UIKit here.
let fromPoint : CGPoint = somePiece.origin
let toPoint : CGPoint = toSquare.coordinates
self.animateView(pieceImageView, fromPoint:fromPoint, toPoint:toPoint)
}
Note that in this design, the view is not holding any model references; the view controller will take care of setting its state and bring upon relevant animations.
If you are overriding drawRect:, then yes, for it be called, you should call setNeedsDisplay to update the changes. The view controller might call or you can add property observers to redraw itself based on a property change. One example for this could be:
class SillyView : UIView {
var drawPonies : Bool = false {
didSet {
if oldValue != drawPonies {
self.setNeedsDisplay()
}
}
}
override func drawRect(rect: CGRect) {
if drawPonies {
self.drawGoodLookingPony()
} else {
self.drawSomeOtherAnimal()
}
}
func drawGoodLookingPony() {
//Draw a good looking pony here
}
func drawSomeOtherAnimal() {
//Draw Something else
}
}
If your view controller decides to draw ponies all you have to do is, get the reference of the SillyView and set drawPonies to true.
self.sillyView.drawPonies = true
You are not passing your data model here, but important pieces of configuration information that will help the view redraw itself.