I have a UIView subclass where hitTest: withEvent: is overridden. Every time a hit test registers, the view logs its tag.
Ex: Click
Console:
You Touched View: 3
You Touched View: 3
You Touched View: 3
My question: Why does it do it three times? I need it to trigger something other than an NSLog and I'm afraid that it will trigger that three times also. I could easily work around the triple trigger, but I was wondering if there was a better explanation.
I suggest you log the event information as well as the uiview's tag. It could be getting called by multiple events - touchDown, touchUp, etc.
Related
Please note this is not an iOS question.
I have an NSView-based app (i.e. not document-based), and I’d like to bolt on a printing subsystem. I can get NSViews in my main controller to print ok. However, I want to have a special view constructed just for printing. The view should not show in the app’s window. The view contains two NSTextFields, two NSTextViews, and 5 labels.
I cannot seem to figure out a way to do this. I have tried various forms of these examples:
Add an NSView to my main view window? Seems logical, but it’s awkward in a storyboard, (I can’t position the view in the storyboard).
Programmatically create a custom NSView with a xib?
For this, I’ve tried:
#IBOutlet weak var printView: NSView!
….
let printOperation = NSPrintOperation(view: printView!)
This results in the comprehensive "fatal error: unexpectedly found nil while unwrapping an Optional value” message.
The outlets are configured correctly (I think)
A seperate ViewController? If so, how can I avoid having two print buttons — one to call the print controller, and the second, to print the PrintController’s view.
I’ve tried reading the Apple docs, but they are not the way I learn best. There are also no Print documents in Swift that I've found. I’ve waded through many SE questions, but have come up blank. Could you point me towards a solution please.
I think the specific problem here is that you're never causing the view to be loaded.
You can double check whether this is the case by overriding the viewDidLoad method on the controller. If it's never called, your view is never loaded from the nib.
Normally the UI machinery takes care of all that when you display a view controller, which is why it can be so confusing when it doesn't happen.
You should be able to trigger the view load by accessing the view property on the controller. e.g.
_ = self.view // Touch the view to force it to load
https://developer.apple.com/documentation/appkit/nsviewcontroller/1434401-view has some additional information.
You can also call loadView() directly although that's usually frowned upon.
In Xcode I have a view which does a modal segue to tab bar controller, and the first view in the tab bar is a UIViewController. This view performs a task on a timed basis (every 15 seconds) using [self performSelector:#selector(blah:) withObject:nil afterDelay:15]. I have some delegate methods that take me back to the root view and calls [self dismissViewControllerAnimated] which in my mind would unload the tab bar controller which would in turn unload the view that is performing the selector every 15 seconds. While those views do disappear after I call dismissViewControllerAnimated, the selectors continue to run every 15 seconds (I see them logging messages in the Xcode console). Am I wrong in my logic that these selectors should cease since their viewcontroller has been unloaded?
Not always. It depends on your classes, if you have a strong reference to the controller or the timer somewhere it may not actually be released. It could also be that since the timer was never invalidated it's keeping the view controller from releasing. Timers are interesting in that they can cause behavior like this if not invalidated, it's always good practice to clean them up when you're done with them. If you're using recursion to call the timed method on itself every 15 seconds that could also keep the view from being released.
If you're using a timer you'll want to invalidate it when appropriate, on view disappear probably. If you want to run once after view disappear you'll need to set a bool so you know when the view disappeared so you can run it once more.
If you're using recursion, be really careful. Something like that can run on a background thread indefinitely. Again if you only want it to run while the view is the active view you can do a check to see which view is on top and only call the method recursively in that scenario, or set a bool on appear and disappear and check the bool when the method calls itself.
I'm trying to release some strain on a view-based NSOutlineView for which I changed a single item property and which I initially reloaded just fine using [myOutlineView reloadData].
I tried [myOutlineView reloadItem: myOutlineViewItem] but it never calls - (NSView *)outlineView:(NSOutlineView *)ov viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item and consequently the data is not updated.
-(void)reloadOutlineViewObject
{
//[myOutlineView reloadData]; //Reload data just fines but is ressource-hungry
NSLog(#"%d",[myOutlineView rowForItem:myOutlineViewItem]; //Making sure my object is an item of the outlineView, which it is !
[myOutlineView reloadItem:myOutlineViewItem];
}
Am I missing something here ?
UPDATE
As pointed out in the comments, my outlineView is view-based.
UPDATE 2
Trying out some stuffs made me realized that the object I am reloading is a second-level object (cf object tree) and calling reloadItem:firstLevelObject reloadChildren:YES does work.
Would it be possible that we can only call reloadItem: on first-level object ? That would be highly inefficient in my case (I only have one two level item and plenty of second level) !
nil ->firstLevelA ->secondLevel1
->secondLevel2
->firstLevelB ->secondLevel3
->secondLevel4
Gonna try to subclass NSOutlineView and rewrite reloadItem: in the mean time.
UPDATE 3
I took a look at NSOutlineView in Cocotron to get start and felt that the code I needed to write to overwrite reloadItem would be quiet heavy. Anyone to confirm ?
I encountered this same problem with a view-based outline view, where calling -reloadItem: seems to just not do anything. This definitely seems like a big bug, though the documentation doesn't explicitly say that reloadItem will reacquire the views for that row.
My workaround was to call NSTableView's -reloadDataForRowIndexes:columnIndexes: instead, which seems to work as expected, triggering a call to the -outlineView:viewForTableColumn:item: delegate method for just that item. You can get the row that needs to be reloaded by calling -rowForItem: and passing in the item you want to reload.
This really isn't a bug - it was something I had explicitly designed. My thought was that reloadItem (etc) should just reload the outline view item properties, not the table cell at that item, since it doesn't carry enough specific information on what to reload (such as what specific cell you might want reloaded). I had intended for people to use reloadDataForRowIndexes:columnIndexes: to reload a particular view based tableview cell; we usually don't provide cover methods when the base class can easily do the same thing with just a few lines of code.
However, having said that, I know multiple people have gotten confused about this, and most people expect it to reload the cell too.
Please log a bug requesting Apple to change this.
thanks,
-corbin
Apple seems to have "fixed" it.
WWDC 2016, presentation 203 "What's New in Cocoa" at 30:35 in the video:
"NSOutlineView
Reloads cell views associated with the 'item' when reloadItem() is called"
reloadItem: works only on macOS 10.12.
From release notes:
https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKit/
NSOutlineView will now reload the cell views associated with ‘item’
when [outlineView reloadItem:] is called. The method simply calls
[outlineView reloadDataForRowIndexes:columnIndexes:] passing the
particular row that is to be reloaded, and all the columns. For
compatibility, this will only work for applications that link against
the 10.12 SDK.
So, if you want to reload row on earlier systems, you should use -reloadDataForRowIndexes:columnIndexes:.
Something like that:
let index = outlineView.row(forItem: obj)
let rowIndex = IndexSet(integer: index)
let cols = IndexSet(0 ... outlineView.numberOfColumns)
outlineView.reloadData(forRowIndexes: rowIndex, columnIndexes: cols)
I'm fairly new to xcode, and am creating a single view application with five views (xib files). I am not using navigation controller or storyboard for two reasons.
If I use Storyboard, I am forced to have my other views be UIView files. These dont have
-(void)viewDidLoad
Which prevents me from loading a few timers I have in my application.
I am able to navigate from the first page to the second and so on, all the way to the fifth page/view with the following code.
-(IBAction)pagethree {
countdown=[[Countdown alloc] initWithNibName:#"Countdown" bundle:nil];
[self.view addSubview:countdown.view];
}
However when I want to go back from the fifth view to the first page I run into a problem.
My first view is called ViewController. If I try the code from above, my app is full of errors.
I also tried using
-(IBAction)back {
[self.view removeFromSuperview];
}
but that only took me back one view.
My fifth view controller is called Statistics and the first one is called ViewController.
What can I code to get from back from the last view to the first?
Thanks
First, to answer your question, you need to do a removeFromSuperview (or similar) for each addSubview: you did. Each time you do addSubview, you're placing your new screen "on top" of the last one, like a stack. When you reach your 5th view, you have 4 other views "behind" it. (Actually, that's not exactly what addSubview does, but I'm just describing the effect in your particular situation).
But I think you're doing it wrong... You should read more about storyboards and navigation controllers. Both are used with UIViewController and support out of the box what you're trying to do by hand. For example, using a navigation controller, to push a new view controller you can do:
[navigationController pushViewController:viewController animated:YES];
You can do that as many times as you want. Then, to go back once, you do:
[navigationController popViewControllerAnimated:YES];
And to go back directly to the first one:
[navigationController popToRootViewControllerAnimated:YES];
Hope this helps.
\I have a view controller which starts a count down timer and updates a button label according to it.The problem is that when the user leaves the view and come back i do not want to restart the timer o continue from the paused time.What would be the best way to active it?
Making the timer in app delegate and post notification to the this view controller.Since the other view controllers do not need it they do not need to listen to any notification.The problem is that i have to make sure that the timer has run down to 0 to enable the button os it means when the view is loaded again i need to wait for the notification at least 1 second to make sure that he time is really up.The problem is what to show during this period on the button label.
If the view controller was a tab bar item i would be no problem but it is a pushedv view contller so it is released when the user taps on back.
Just create a singleton object for managing your timer(s). You can create it from appdelegate.
You can get remaining time from that object anytime you want (firedate - datenow). So you do not need to work with notifications. It is also not good idea to send a notification in every second, which is not used by your view, when it is not allocated. When your view is allocated / comes to screen can ask that object every second the remaining time and updates his label or some subviews etc. You can implement it easily with a NSTimer object.
You can also place a bool variable in that singleton object, which indicates, if firedate reached. Or check remaining time < 1 . So your view check the value of the variable while is allocating/showing then you can decide to activate or deactivate your button.