How are NSTableCellViews supposed to be laid out? - xib

I have a fairly basic MainWindow.xib with a source list-style sidebar. I created it by dragging the Source List template into the window, which already contains two NSTableCellViews: HeaderCell and DataCell.
The latter consists of an icon (using NSImageView) and a label (NSTextField). Instead, I want the label and another, smaller label underneath. In IB, this looks as follows:
If I focus on just DataCell, it highlights accordingly:
Thing is, actually running the program, it looks nothing like the template:
Notice how the two NSTextFields just get smashed together into one. My understanding was that view-based NSOutlineViews (and view-based NSTableViews, for that matter) are supposed to be designed as a template from within IB. Instead, the dimensions from the template seem to get mostly ignored.
Here's the code that sets the view's values from the data source:
public class TourSourceListDelegate : NSOutlineViewDelegate
{
public override bool IsGroupItem(NSOutlineView outlineView, MonoMac.Foundation.NSObject item)
{
return (item as TourSourceListDataSource.Item).IsHeader;
}
public override NSView GetView(NSOutlineView outlineView, NSTableColumn tableColumn, MonoMac.Foundation.NSObject item)
{
if (IsGroupItem(outlineView, item))
{
return outlineView.MakeView("HeaderCell", this);
}
else
{
var data = item as TourSourceListDataSource.Item;
var dataView = outlineView.MakeView("DataCell", this);
(dataView.Subviews[0] as NSTextField).StringValue = data.Name;
(dataView.Subviews[1] as NSTextField).StringValue = data.Date_start.ToShortDateString();
return dataView;
}
}
}
I've tried overriding GetRowHeight, but that doesn't seem to resolve the problem (it makes more room, but still doesn't let the views distribute themselves properly), nor does it seem necessary.
I've also tried playing with the various Autosizing, Autoresizes Subviews, etc. toggles in IB, but that doesn't seem to produce intuitive results, and again, it doesn't seem necessary — the view as presented in IB is exactly what I want, just with slightly longer labels in practice.
I haven't tried converting this to AutoLayout yet.
What obvious step am I missing?
Some more info that probably doesn't make a difference: this is a Xamarin.Mac/MonoMac project with Xcode 5.0, MacOSX10.8.sdk, Xamarin Studio 4.0.12, Xamarin.Mac 4.0.12, and Mono 3.2.3 (targeting Mono / .NET 4.0). I've also enabled App Sandboxing.

What's important in interface builder is the view hierarchy. What kind of view is that cell? Are those labels really subviews of the cellview or not? The hierarchy should look something like:
One thing that's fishy that I see is accessing dataView.Subviews[0] and [1]. If you're adding subviews to your cells then should be creating your own NSTableViewCell subclasses, with each view connecting to the subclass' IBOutlet properties. The subclass doesn't need any code in its implementation, just the declaration of its properties in #interface, such as titleField and descriptionField, and an empty #implementation that auto-synthesizes them.
Then makeViewWithIdentifier (or apprently the glue MakeView in Xamarin) when passed the right identifier should create your NSTableViewCell subclass, and at runtime you can verify that using po dataView in the debugger. Then you access the subviews using the properties of your NSTableViewCell subclass' interface instead of assuming which view is in which position with the subview array, using dataView.titleField and dataView.descriptionField.
If your cell view has one text field then you can use NSTableViewCell without subclassing, but do connect up the textField outlet (its connected by default as long as you don't delete & recreate the cell view's label view) so you can access it through the property, again instead of having to dive into the subviews array.
All that said, it's not really clear why you're seeing what you are. It looks like those aren't the subviews you expect, and might even look like the wrong fonts as well as in the wrong positions. Using a custom subclass of NSTableViewCell and verifying its class at runtime is a good way of making sure it's creating the view you expect, but you can also dump the subview within the debugger using po [dataView _subtreeDescription].

Related

iOS UITest - Navigate to all available screens

I am using iOS UITest for a Swift application. I use something like,
func testAllScreenNavigation() {
let app = XCUIApplication()
app.tabBars.buttons["Home"].tap()
app.navigationBars["Home"].buttons["More"].tap()
app.sheets.buttons["Cancel"].tap()
}
etc. to navigate some of the specific, tabs, buttons, etc. and switch to respective screens. But i want to navigate each and every screens of my Application (It can be BFS style navigation or DFS style navigation, no matter). Is there any way iOS provides so i can get all navigable elements and then explore deeper and deeper automatically for my App?
I also need to keep trace of which xcuoelement in a screen is already processed and which are not yet processed.
The only way I can think of is using Xcode UI test recorder feature.
While you are recording, navigate through all of your screens via the device/simulator and then the XCUIApplication() variable would be recorded with the appropriate references.
If the button/nav bar/any element has text on it, it will show up in the recorded code or else it will be referenced numerically.
Hope that helps.
Kind regards,
Mukund
I like your idea for getting all views and check whether the layouting and localization for example is fine.
I think you need to specify your criteria for "screens" and how they are accessed.
Basically, one could thing of the following structure
- UITabBarController
-- UISplitViewController
--- UINavigationController
---- UIViewController
----- UIBarButtonItems
----- UIView
----- UIButton
----- UISwitch
----- UITableViewCell
You could now go top down from the UITabBarController to the next controlling instance (might also skip one, e.g. SplitViewControllers on iPhones).
You can use the general property:
XCUIApplication().tabBars
Nevertheless that transition is the problem: How would you get from one ViewController to another and are they all position in the ViewController's View or do you have to loop the subviews of a view.
UIButton -> Touch Up Inside
UISwitch -> Value Changed
UITableViewCell -> DidSelectRowAtIndexPath
UIView -> UILongPressGestureRecognizer
This is how I would basically set it up:
For each UIViewController instance, get the related View (and perform the following call recursively).
Check all the subviews of a view.
For UIViews, go even further and check their subviews
For UIButtons, perform TouchUpInside
and so on.
Make sure to have a condition to stop going deeper, as UITableViews got a lot of subviews or your UIWebViews would of course be set up in a different way.
This way you should be able to navigate through a lot Views in your app hierarchy, but you will need some extensions for UIBarButtonItems, custom Gesture Recognizers and of course also for your "special" controls that might listen to value changes and perform a layout-change.
Accessing specific elements
In addition to the above approach where you simply get an array of elements of a specific type, you can access specific elements (e.g. those where you know they are of a very specific type with certain ValueChangeListeners or something)
To access a specific object in particular, like the TabBar example from above, you can use the accessibilityLabel like so. At first you need to declare the accessibilityLabel in your code or in the .xib-file/.storyboard:
// just to illustrate, so you get an idea:
self.tabBarController.isAccessibilityElement = true
self.tabBarController.accessibilityLabel = "tabBar"
And then do:
let tabBar = XCUIApplication().tabBars["tabBar"]
Here is Apple's documentation for setting these accessibilityLabels:
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/iPhoneAccessibility/Making_Application_Accessible/Making_Application_Accessible.html
A great way to get the related identifier of an element would be to use the Accessibility Inspector from Apple:
https://developer.apple.com/library/content/technotes/TestingAccessibilityOfiOSApps/TestAccessibilityiniOSSimulatorwithAccessibilityInspector/TestAccessibilityiniOSSimulatorwithAccessibilityInspector.html
Accessing elements in general
To access elements in general, you need to make use of the XCUIElementType of these objects, here you will access the objects based on their classes.
E.g. you could call:
"tabBars", "navBars", "tables", "buttons", and so on from the elements in general.
Still you would be facing the issue with "special controls". As the Apple documentation lacks (imho) some detail about properties and attributes, I do recommend the docs here: https://blog.metova.com/guide-xcode-ui-test/ It provides a great overview of what is accessible and may help you getting some better understanding.
An overview of the available XCUIElementTypes can be found here. Basically, the elementType property is an enumerated value that represents the type of an element. XCUIElementType is a very large enumeration and some of its members do not apply to iOS applications (they apply to MacOS X apps). Some of the more commonly used values are:
Alert
Button
NavigationBar
TabBar
ToolBar
ActivityIndicator
SegmentedControl
Picker
Image
StaticText
TextField
DatePicker
TextView
WebView
https://developer.apple.com/reference/xctest/xcuielementtype?language=objc

How to access custom view properties after initialize

I'm targeting IOS 8+.
I have a form that is used in more than one place. So I decided to create a custom view where I define the various "form" text fields.
I have built my XIB, and the UIView subclass contains the outlets for each textField.
The view is composed of a background image and a scroll with the form fields over it.
Now, my first obstacle was: I need to have this custom view in a container that may or may not have a navigation bar. This made me create a constraint outlet so I could update its value to push down the scroller view. This way I'd have the whole image in the frame, the top being behind the navbar and the scroller bellow the nav bar).
Here's a manual drawing to help understanding the problem.
It's very possible that I'm making a lot of mess and confusion on my way to solve this. :)
The problem is:
After awakeFromNib runs I have no access to the constraint property. I then noticed the same thing happens for the TextFields outlets.
So, how can I access the custom view's properties when I instantiate them programatically?
Something like:
Controller:
let customView = SignupView(frame: f)
view.addSubview(customView)
customView.pushScrollerDownBy(50.0)
Custom view:
func pushScrollerDownBy(yOffset: CGFloat) {
//topScrollerConstraint is the outlet for the textField.
topScrollerConstraint.constant = yOffset //right now topScrollerConstraint is nil.
}
You should check if you have connected your topScrollerConstraint to the file's owner since it will not get instantiated and therefore, error. Here is a recent SO question regarding difference between these two:
What is File’s owner in XIB in this case?

Global hook for showing UIViews

I have an iOS app I'm working on using Xamarin and MVVMCross, but I am also using a third-party native library which includes some views of it's own (loaded from .xib files with the implementation in the library). What I need to do is set some properties on those native views and I'm trying to see if there's a way to do it that doesn't involve jumping into xcode and trying to recompile that whole thing (because I can't get that working at the moment).
So my question is, is there a way to intercept, application-wide, all attempts to load a view so that I can examine the view and if it's one of those from the third-party library, set some properties on it before it's displayed?
MvvmCross has a MvxTouchViewPresenter which has a ChangePresentation property, but it seems to only apply to MvxViewController loaded by MvvmCross itself.
You can very easily intercept all attempts to access a viewmodel by overriding the Show() method on your MvxTouchPresenter. For example:
public override void Show(MvxViewModelRequest request)
{
IMvxTouchView view = this.CreateViewControllerFor(request);
UIViewController viewController = (UIViewController) view;
this.Show(view);
}
You can then examine all Views in the UIView heirarchy by using something similar to the Objective-C code in this post. You just need to walk through all the UIViews in the viewController property and identify your view (perhaps by "smelling it" with respondsToSelector; I can't figure out exactly how you'd use isKindOfClass if Xamarin doesn't know it).
I hope I understood your question. Let me know if there's anything else missing.

Where do I create custom UI elements?

A quick question from a wanna-be iOS developer. I want to create a UI for an iPhone app without Interface Builder, only programmatically. However, I want to stick to MVC recommendations and separate V and C and have a clean readable code, therefore:
I create UIView class files (e.x. SplashView.h and SplashView.m)
I create UIViewController class files (SplashViewController.h and SplashViewController.m)
I define my UI elements (view, subviews, buttons and text fields) in the UIView class files
I load the main view in view controller's loadView method, and then do other things in view controller's viewDidLoad method
Is this a correct approach to begin with?
Second part of the question, independent of Y/N answer to the first. Where do I define these custom UI elements?
- Inside the view's initWithFrame: method?
- In separate (property getter? property setter?) methods? I.e. do I have to declare each UI element as a property first in the .h file?
If these questions sound a bit ignorant, it must be because they are :) I found lots of sample code on StackOverflow, but little to indicate where you actually put it. I would be really grateful for any help, especially if you could paste/reference some relevant code.
Your list is correct. This is how I do all of my apps. No Interface Builder, just code.
Each custom view typically creates its own subviews in an appropriate initXXX method. This could be initWithFrame: but you could define others as needed. Subview layout can be done through constraints, autoresizing masks, or by implementing layoutSubview.
Each view controller would instantiate its needed views in the viewDidLoad. View layout can be done with constraints, autoresizing masks, or by implementing viewWillLayoutSubviews.
The use of properties is completely optional. Create public properties for anything to be set/get from an outside class. Optionally create private properties for values internal to the implementation.
Go to the Apple website for Sample Code; download everything that you can for applications that are similar to your goal.

monotouch - reusable iOS custom view

OK, so this one is probably a bit challenging, as it is monotouch...
I have a reusable custom toolbar that that I plan to reuse on multiple screens.
I want to create the toolbar in Interface Builder
I want to be able to update labels on the toolbar from ANY view controller. So I might have five different view controllers that all have this toolbar, and can update labels on it.
So my question is:
How do I even begin to subclass UIView in MonoTouch? If I create a new "iPhone View", all it gives me is a nib. I have no place to hook up outlets or actions.
Given that you are able to tell me how to set up MonoTouch to have C# code files for subclassing UIView so I can handle events and access properties, how do I hook it up to the nib? I see a lot of people saying to use initWithFrame to call loadNibNamed in Objective-C, but this doesn't correctly translate to MonoTouch. How would I do that in MonoTouch?
Given that #1 and #2 are fulfilled, how do I load this custom toolbar into any viewcontroller, and add it at specific coordinates on the screen? Doing the initWithFrame CGRect stuff doesn't seem to have a simple path in MonoTouch
If anyone is able to figure this out, you are amazing!!!!
Thanks you in advance!
So after a lot of digging, I finally found the answer.
The most informative tutorial was here:
http://sgmunn.com/blog/2012/03/using-loadnib-to-load-a-view/
Essentially, you need to create custom subviews, set them up in the interface builder, then do some simple mapping on the MonoTouch side. But the key point that was throwing me off from the example was this:
You must, when subclassing UIView, implement the base constructor for IntPtr
When you do that, everything falls into place. You have your Outlets and Actions hooked up, and can manipulate them at will.
EDIT: To be more descriptive than just linking...
How do I even begin to subclass UIView in MonoTouch? If I create a new
"iPhone View", all it gives me is a nib. I have no place to hook up
outlets or actions.
Create a new iPhone view, it'll create a nib. Open the nib, set the "custom class" to your custom class. MonoTouch should generate it automatically. If it doesn't, create a new C# class and subclass UIView, but make sure you create the constructor for IntPtr. If you use Storyboards, when you call Storyboard.InstantiateViewController() it'll fill in the IntPtr value for you. Note that if you DO use Storyboard.InstantiateViewController, you need to set the identifier as well to your custom class' name.
Once you create that iPhone view, you should be able to open up "code view" in IB and hook up your outlets and actions.
Given that you are able to tell me how to set up MonoTouch to have C#
code files for subclassing UIView so I can handle events and access
properties, how do I hook it up to the nib? I see a lot of people
saying to use initWithFrame to call loadNibNamed in Objective-C, but
this doesn't correctly translate to MonoTouch. How would I do that in
MonoTouch?
There is no initWithFrame: or CGRect in MonoTouch, you use RectangleF. If you load the nib programmatically, you can use the constructor that takes in a RectangleF and use that to set x, y, width, height.
Given that #1 and #2 are fulfilled, how do I load this custom toolbar
into any viewcontroller, and add it at specific coordinates on the
screen? Doing the initWithFrame CGRect stuff doesn't seem to have a
simple path in MonoTouch
For Storyboards use Storyboard.InstantiateViewController (make sure you set the identifier in IB). For everything else, use:
var nibObjects = NSBundle.MainBundle.LoadNib("YourViewName", theController, null);
var instantiatedView = (YourClassName)Runtime.GetNSObject(nibObjects.ValueAt(0));
as depicted in the above link.
Enjoy! - Allison.
Instead of loading Nib files from iOS 9 onwards you can use container views and storyboard reference to achieve the same.
You can check here

Resources