basically I have this
#implementation MyView : CPView
{
CPArray MyPanelArray;
}
// Populate the MyPanelArray and position each panel
- (void)initMyView
{
...
}
MyPanels are pretty much wrappers for images. When everything is initialized it draws just fine. Then I have a slider to manipulate the position of the images and the only way I know how to redraw everything is to overwrite the MyView with a new instance and in the main contentView do something like
// Has the correct effect, but feels wrong
- (void)sliderAction:(id)sender
{
var myNewView = [MyView initWithPositionValue:[sender value]];
[_contentView replaceSubview:_myView with:myNewView];
_myView = myNewView;
}
It works all right, but I doubt thats the "right way".
*I know I can use a CPCollectionView for a basic setup, but its not going to work for what I'm trying to accomplish.
Thanks in advance.
By "redraw" do you mean actually doing a drawRect: or just moving/resizing the image views? If it's the latter then you can just call setFrame: on _myView.
Related
I have an animation using two views where I would call lockFocus and get at the graphics context of the second NSView with [[NSGraphicsContext currentContext] graphicsPort], and draw. That worked fine until macOS 10.14 (Mojave).
Found a reference here:
https://developer.apple.com/videos/play/wwdc2018/209/
At 22:40 they talk about the "legacy" backing store that has changed. The above lockFocus and context pattern was big on the screen, saying that that won't work any more. And that is true. lockFocus() still works, and even gets you the correct NSView, but any drawing via the context does not work any more.
Of course the proper way to draw in a view is via Nsview's drawRect. So I rearranged everything to do just that. That works, but, drawRect has already automatically cleared the "dirty" area for you, prior to calling your drawRect. If you used setNeedsDisplayInRect: it will even clear only those areas for you. But, it will also clear areas made up of more than one dirty rectangle. And, it clears rectangular areas, while I draw roundish objects, so I end up with too much cleared away (black area):
Is there a way to prevent drawRect to clear the background?
If not I will have switch to using the NSView's layer instead, and use updateLayer, or something.
Update: I am playing around with using the layers of NSView, returning TRUE for wantsLayer and wantsUpdateLayer, and implementing:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
That is only called when I do:
- (BOOL) wantsLayer { return YES; }
- (BOOL) wantsUpdateLayer { return NO; }
and use setNeedsDisplayInRect: or setNeedsDisplay:
Which indeed makes drawRect no longer called, but the same automatic background erase has already taken place by the time my drawLayer: is called. So I have not made any progress there. It really is the effect of setNeedsDisplayInRect, and not my drawing. Just calling setNeedsDisplayInRect causes these erases.
If you set:
- (BOOL) wantsLayer { return YES; }
- (BOOL) wantsUpdateLayer { return YES; }
the only thing that is called is:
- (void) updateLayer
which does not provide me with a context to draw.
I had some hope for:
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawNever;
The NSView doc says:
Leave the layer's contents alone. Never mark the layer as needing
display, or draw the view's contents to the layer. This is how
developer created layers (layer-hosting views) are treated.
and it does exactly that. It doesn't notify you, or call delegates.
Is there a way to have complete control over the content of an NSView/layer?
UPDATE 2019-JAN-27:
NSView has a method makeBackingLayer that is not there for nothing, I guess. Implemented that, and it seems to work, basically, but no output shows on screen. Hence the followup question: nsview-makebackinglayer-with-calayer-subclass-displays-no-output
Using NSView lockFocus and unlockFocus, or trying to access the window's graphics contents directly not working in macOS 10.14 anymore.
You can create an NSBitmapImageRep object and update drawing in this context.
Sample code:
- (void)drawRect:(NSRect)rect {
if (self.cachedDrawingRep) {
[self.cachedDrawingRep drawInRect:self.bounds];
}
}
- (void)drawOutsideDrawRect {
NSBitmapImageRep *cmap = self.cachedDrawingRep;
if (!cmap) {
cmap = [self bitmapImageRepForCachingDisplayInRect:self.bounds];
self.cachedDrawingRep = cmap;
}
NSGraphicsContext *ctx = [NSGraphicsContext graphicsContextWithBitmapImageRep:cmap];
NSAssert(ctx, nil);
[NSGraphicsContext setCurrentContext:ctx];
// Draw code here
self.needsDisplay = YES;
}
You can try overriding method isOpaque and return YES from there. This will tell OS that we draw all pixels ourselves and it do not need to draw the views/window at the back of our view.
- (BOOL)isOpaque {
return YES;
}
Is it possible to adjust the blur radius and transparency of an NSVisualEffectView when it's applied to an NSWindow (Swift or Objective-C)? I tried all variations of NSVisualEffectMaterial (dark, medium, light) - but that's not cutting it. In the image below I've used Apple's non-public API with CGSSetWindowBackgroundBlurRadius on the left, and NSVisualEffectView on the right.
I'm trying to achieve the look of what's on the left, but it seems I'm relegated to use the methods of the right.
Here's my code:
blurView.blendingMode = NSVisualEffectBlendingMode.BehindWindow
blurView.material = NSVisualEffectMaterial.Medium
blurView.state = NSVisualEffectState.Active
self.window!.contentView!.addSubview(blurView)
Possibly, related - but doesn't answer my question:
OS X NSVisualEffect decrease blur radius? - no answer
Although I wouldn't recommend this unless you are ready to fall back to it not working in a future release, you can subclass NSVisualEffectView with the following to do what you want:
- (void)updateLayer
{
[super updateLayer];
[CATransaction begin];
[CATransaction setDisableActions:YES];
CALayer *backdropLayer = self.layer.sublayers.firstObject;
if ([backdropLayer.name hasPrefix:#"kCUIVariantMac"]) {
for (CALayer *activeLayer in backdropLayer.sublayers) {
if ([activeLayer.name isEqualToString:#"Active"]) {
for (CALayer *sublayer in activeLayer.sublayers) {
if ([sublayer.name isEqualToString:#"Backdrop"]) {
for (id filter in sublayer.filters) {
if ([filter respondsToSelector:#selector(name)] && [[filter name] isEqualToString:#"blur"]) {
if ([filter respondsToSelector:#selector(setValue:forKey:)]) {
[filter setValue:#5 forKey:#"inputRadius"];
}
}
}
}
}
}
}
}
[CATransaction commit];
}
Although this doesn't use Private APIs per se, it does start to dig into layer hierarchies which you do not own, so be sure to double check that what you are getting back is what you expect, and fail gracefully if not. For instance, on 10.10 Yosemite, the Backdrop layer was a direct decedent of the Visual Effect view, so things are likely to change in the future.
I had the same issue as you had and I have solved it with a little trick seems to do the job that I wanted. I hope that it will also help you.
So in my case, I have added the NSVisualEffectView in storyboards and set its properties as follows:
and my View hierarchy is as follows:
All that reduces the blur is in the NSViewController in:
override func viewWillAppear() {
super.viewWillAppear()
//Adds transparency to the app
view.window?.isOpaque = false
view.window?.alphaValue = 0.98 //tweak the alphaValue for your desired effect
}
Going with your example this code should work in addition with the tweak above:
let blurView = NSVisualEffectView(frame: view.bounds)
blurView.blendingMode = .behindWindow
blurView.material = .fullScreenUI
blurView.state = .active
view.window?.contentView?.addSubview(blurView)
Swift 5 code
For anyone interested here is a link to my repo where I have created a small prank app which uses the code above:
GitHub link
I am creating a tableview where I will have an UIImageViewinside a UITableViewCell that I would like to allow the user to zoom into. I have been able to create this effect inside a UIScrollView but have struggled to get it right for zooming into a single UITableViewCell.
At the bottom I have included the code that has worked for me for the UIScrollView. The challenge starts with what to return for the viewForZoomingInScrollView. If I try to give this method the UIImageView from the cell It throws an errors as it appears the viewForZoomingInScrollView is called before the table is loaded thus it can not find the cell I am referencing. If I pass the entire view itself self.view I can scroll but the entire tableview is zoomed (where I would like to zoom just the UIImageView inside the cell) and when I release the zoom I get an error Terminating app due to uncaught exception 'CALayerInvalidGeometry', reason: 'CALayer bounds contains NaN: [nan nan; 375 667] which is clearly not the correct path to take.
Any help or guidance here would be appreciated.
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
[self centerScrollViewContents];
}
- (void)centerScrollViewContents {
CGSize boundsSize = zoomTable.bounds.size;
CGRect contentsFrame = self.imageView.frame;
if (contentsFrame.size.width < boundsSize.width) {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0f;
} else {
contentsFrame.origin.x = 0.0f;
}
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f;
} else {
contentsFrame.origin.y = 0.0f;
}
self.imageView.frame = contentsFrame;
}
It sounds to me as though you want something like what Facebook or Twitter have, where you tap on an image and it zooms to fit the screen.
What you need to do is to consider that as a navigational step -- i.e. conceptually similar to what happens if you select a row in a standard table view and it pushes a new view controller onto the navigation controller stack, except that you probably want to present modally using a custom transition.
In the simple case, this would mean adding a tap gesture recogniser to the cell's image view; for the full effect you would add a pinch gesture recogniser to make an interactive transition.
I'd recommend watching the following WWDC videos:
2013 Session 218 "Custom Transitions Using View Controllers"
2014 Session 214 "View Controller Advancements in iOS 8"
2014 Session 228 "A Look Inside Presentation Controllers"
From your question What I understand is that you wanted to implement the functionality of Zoom in and Zoom out for ImageView. You first implemented that in a sample where you had a ScrollView Inside a ViewController and then ImageView inside that ScrollView. Things worked for you as expected. But then when you wanted the same functionality inside the TableViewCell, things didn't work for you.
So, I am suggesting you to use a View inside ScrollView as a ZoomView, and that ZoomView should have the ImageView as subview. And return that ZoomView in the method:
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView { return self.ZoomView; }
I've never dealt much with NSViews so I am at a loss on this. I have a custom class that contains an NSImage ivar. I also have a subclass of NSView that contains an array of instances of my custom class. The NSImage from the custom class gets drawn at the center of the NSView upon creation. I need to be able to drag that NSImage around the NSView freely.
I have implemented the following methods to get started:
-(void)mouseDown:(NSEvent *)theEvent
{
[self beginDraggingSessionWithItems: /* I don't know what to put here */
event:theEvent
source:self];
}
I have tried placing the NSImage object from my custom class in an array as the item to drag but an exception is raised. I also tried creating an NSDraggingItem to place in there but it could not write the object to the pasteboard and raised an exception.
-(void)draggingSession:(NSDraggingSession *)session willBeginAtPoint:(NSPoint)screenPoint
{
NSLog(#"began");
}
-(void)draggingSession:(NSDraggingSession *)session movedToPoint:(NSPoint)screenPoint
{
NSLog(#"moved");
}
-(void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
{
NSLog(#"ended");
}
These methods get called properly as I click and drag my mouse around my subclass of NSView however they are useless until I can get the object to move.
I believe the answer may be to create an NSDraggingItem for each image and use that to move them freely around the view however I could not get it to work with even one object and do not know how to properly implement this idea. Any help is appreciated.
Went through a lot of headache on this one but finally figured it out. Instead of using beginDraggingSessionWithItems:event:source I used dragImage:at:offset:event:pasteboard:source:slideBack:. I keep track of the coordinates of each image in the view and can tell whether a specific image was clicked on or not by utilizing the following method I wrote in mouseDown:
-(BOOL)clickedAtPoint:(NSPoint)point InsideRect:(NSRect)rect
{
if ((point.x < (rect.origin.x + rect.size.width) &&
point.y < (rect.origin.y + rect.size.height)) &&
point.x > rect.origin.x && point.y > rect.origin.y) {
return YES;
}
return NO;
}
i would have a uiview which is always visible in every zoom scale.
in the loadView method in PhotoViewController.m i tried following:
[self.view addSubview:test];
[self.view bringSubviewToFront:test];
... with no success....
can someone help me?
greets, phil
as far as my knowledge there is a delegate method called after the zooming action
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
//write the bringsubviewtofront code here
}
It may works