In the DetailView of my SplitViewController, I placed several UIViews as containers for different flat TableViews. So far my intention. To keep things simple, I wanted to add DialogViewControllers (MvxDialogViewControllers to be honest, but that didn't work) to these SubViews. My problem is, that the Dialogs overlap their container and "use" more space, then they should (I can see the default grey background around the container views).
Since I thought the frame of the DialogViewController depends on the dimension of its parents frame, I am little bit confused. I create and add the DVC like this (ParentViewController derives from MvxViewController):
public partial class DetailView : BaseView
{
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
CreateVisitContextController();
// ...
}
private void CreateVisitContextController()
{
var root = new RootElement("Test")
{
new Section("Section1"),
new Section("Section2")
};
var dialogViewController = new DialogViewController(root);
VisitContextContainer.AddSubview(dialogViewController.TableView);
}
Any hint or assistance would really be appreciated. Perhaps this is totally the wrong direction?
One remark: I designed the DetailView and the corresponding container SubViews in IB.
When I use DialogViewController without inheriting from MvxDialogViewController, will I still be able to bind to the Dialog Elements?
Thanks in advance and have a nice evening!
"use" more space, then they should
It's worth spending some time to look at uikit and how it works. A uiview does not always auto-resize to its container.
There are quick fixes like q&a's here like: Makeing view resize to its parent when added with addSubview - but it might be better to start from the apple uikit reference - http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIKit_Framework/_index.html
Related
I know theres countless similar questions on this that either all result in using flexible height/width or setting translatesAutoresizingMaskIntoConstraints to false.
I have add a view using an extension I created:
extension UIView {
func addView(storyboard: String, viewIdentier: String) {
let story = UIStoryboard(name: storyboard, bundle: nil)
let subview = story.instantiateViewController(withIdentifier: viewIdentifier)
subview.view.frame = self.bounds
self.addSubview(subview.view)
}
}
If I use this to initialise a view, in the ViewDidAppear everything works fine. But if its in the view did load then the constraints are all over the place because the contrainView that contains the view has its own constraints that are not yet loaded.
I currently use like this:
#IBOutlet weak var container: UIView!
override func viewDidLoad() {
container.addView(storyboard: "Modules", viewIdentifier: "subview")
}
I don't want to initialise the view in the ViewDidAppear because of reasons. Is there any way of fixing this so it works as expected? Or reload the subview constraints after the superview has loaded?
Also I've really tried using other solutions. I can't make this work so would really appreciate some help.
There is no way to do things in the hard-coded manner your code proposes. You are setting the frame of the subview based on self.bounds. But in viewDidLoad, we do not yet know the final value for self.bounds. It is too soon.
The appropriate place to do something that depends on knowing layout values is after layout has taken place, namely in viewDidLayoutSubviews. Be aware that this can be called many times in the course of the app's lifetime; you don't want to add the subview again every time that happens, so use a Bool flag to make sure that you add the subview only once. You might, on subsequent resizes of the superview, need to do something else in viewDidLayoutSubviews in order to make appropriate adjustments to the subview.
But the correct way to do this kind of thing is to add the subview (possibly in viewDidLoad) and give it constraints so that its frame will henceforth remain correct relative to its superview regardless of when and how the superview is resized. You seem, in your question, to reject that kind of approach out of hand, but it is, nevertheless, the right thing to do and you should drop your resistance to it.
In the iOS world, we're all used to the following pattern:
class UIViewController {
open var view: UIView!
}
A ViewController is obviously a controller controlling the view, which contains a lot of subviews.
Now, I have a lot of subviews that I want to reuse, and I want to enrich them with more functionalities. Think of a UISlider, a UILabel, or a UITableView that react to some events or some changes in the model. These subviews also need to be #IBDesignable with IBInspectable properties for customisation purposes. I also want to share those components through a library as well.
So in a way, I want small controllers controlling those subviews that will end up in the view of the ViewController.
I am thinking of doing this for the UIKit classes:
#IBDesignable
public class CustomSlider: UISlider {
}
That is a nice way to be able to provide the component with customisation options. The downside is that we're using inheritance here (would rather use composition), and I'm not sure if CustomSlider is really considered here a controller or not.
Can anyone tell me what are good practices around creating controllers for subviews that are customisable? Thanks in advance!
EDIT: Specific case for Views that have delegates and datasource:
#objc public class CustomTableView: UITableView, UITableViewDataSource, UITableViewDelegate {
#IBInspectable public var someCustomField: UInt = 0
public override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
dataSource = self
delegate = self
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
dataSource = self
delegate = self
}
// Implement UITableViewDataSource and UITableViewDelegate
}
Is this bad pattern to have datasource = self and delegate = self, or is it ok?
It's a judgement call. I'd say if all you are doing is adding features to a single view class, it is probably better to just use a custom subclass like your CustomSlider example.
If, on the other hand, you're using small suites of objects, like a slider, a text field, a segmented control and a couple of labels, you might want to think about using container views, embed segues, and custom view controllers. Think of that as setting up tiles that manage sets of UI elements. You can put such a view controller anywhere you want, and size it as needed.
You could also create a custom controller object that manages one or more custom views, but there isn't really system "plumbing" for that, so the burden is on you to build mechanisms to support that approach. You have to teach your view controllers to talk to a controller object that isn't a view, but that HAS views inside it.
Apple does what you're talking about in a couple of instances: UITableViewController and UICollectionViewController, and I think they didn't do it right. A UITableViewController or UICollectionViewController can manage only a single view. You can't put a button at the bottom, or a label somewhere, or a segmented control. The content view of one of those specialized view controllers must be the corresponding view object, which limits their usefulness. You can, of course, get around the problem by using container views and embed segues, and that leads me to my suggestion.
EDIT:
As far as making a view object it's own data source, I would call that "a violation of the separation of powers". A table view is a view object, and the data source is a model object. By combining them, you're collapsing the view and the model into one. That's going to create a larger, very specialized object that is less likely to be reusable.
The same goes for making an object it's own delegate. The idea of the delegate pattern is to be able to leave certain decisions about an object's behavior up to somebody else, so that its more flexible. By making it its own delegate you are limiting the table view's range of behavior and making it less reusable.
Neither thing is going to "warp your mind, curve your spine, and make the enemy win the war" but they seem ill-advised.
I'm looking for a way to show a UIView "InventoryView" in 2 view controllers.
I'm working on an inventory system for my game that I trying to make but I need to be able to access it from my main view, where it will go to a InventoryViewController (in this ViewController is my InventoryView) but I also need to be able to access the InventoryView from my BattleViewController where it does not go to my InventoryViewController but where it print the InventoryView on my BattleViewController so I can access everything durning the battle.
Example:
(evrything is dragand drop, the UIView and the UIButtons)
InventoryViewController
class InventoryViewController: UIViewController {
class InventoryView: UIView {
//here are some UIButtons and labels
}
}
BattleViewController
class BattleViewController: UIViewController {
class InventoryView: UIView {
//it should print the Inventory Screen on my BattleViewController
//here are the same properties as it shows in the InventoryViewController
}
}
This is a great example to look at the way OOP programming works best.
Ask yourself the following questions:
What is the purpose of the view?
Are the interactions on the view homogenous across all the instances? (touch events, specific behavior, etc...)
What is the minimum amount of information you need to make the view look the way you want?
Once you have those answers, you can approach the concept of reusability of views safely.
The way to go about it is to subclass UIView, create the necessary elements of your view, setup your constraints (still in the view, either in a nib or programmatically), and implement any behavior that will be consistent across views (For example if the view is a segmented control, every time you click a segment all the others go grey and the one you clicked go blue. Since that's the primary purpose of the segmented control, the code for it should belong to the segmented control).
Chances are you will find the docs very useful: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/
Lastly write a setup method that takes all the information you need and sets up all your graphical elements accordingly. Remember, views should never own their data (they should be templates, the controller will provide the data).
I have no idea what you view looks like but I assume the inventory is represented as an object. Then something like could be a good start:
class InventoryView: UIView {
var inventory: Inventory? {
didSet {
if let newInventory = inventory { //in case we set it to nil
setup(withInventory: newInventory)
}
}
}
private func setup(withInventory inventory: Inventory) {
//do your setup here
}
}
Then in your controller you can call:
let inventoryView = InventoryView()
inventoryView.inventory = myPlayer.inventory
You cannot use a view in two places, at least not within the UI. Every view can be added to only one super view at a time.
If you need the same contents to be displayed twice, create a UIViewController class which's view contains the common UI, create two of those and add them to your UI.
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?
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].