Why layoutSubviews called twice? - swift2

I have subClass an UIView and override the layoutSubviews method.
When I called this subClass in my viewController,and add it to the controller's view,I found that "layoutSubviews" function have been called twice.
coverFlowView = CoverFlowView(frame: CGRectMake(0, 40, UIScreen.mainScreen().bounds.size.width, 480))
coverFlowView.delegate = self
coverFlowView.dataSource = self
self.view.addSubview(coverFlowView)
CoverFlowView is a view subclass UIView

I encountered the same problem.I tried different OS and got different result for same code flag:
one device only call layoutSubViews once, but another one called twice which caused one bug for my code...
My Code:
CGFloat step = 0.0;
if (self.array_rateOfEachColor) {
CGFloat sumRate = 0.0;
for (NSString *rate in self.array_rateOfEachColor) {
sumRate +=[rate floatValue];
}
NSAssert(sumRate != 1.0 , #" sum of all elemetns for array array_rateOfEachColor must be 1.0 ");
for (int i = 0 ; i < [self.mutableArray_layers count]; i ++) {
CGFloat step = [[_array_rateOfEachColor objectAtIndex:i] floatValue];
CAShapeLayer *tmp = [self.mutableArray_layers objectAtIndex:i];
[tmp setStrokeStart:self.strokeEndValue];
if (i == (self.mutableArray_layers.count -1)) { //the last layer
self.strokeEndValue = 1.0 - self.space ;
[tmp setStrokeEnd:self.strokeEndValue];
}else {
[tmp setStrokeEnd:self.strokeEndValue + step ];
}
self.strokeEndValue += (step + self.space); // record last strokeEndValue
}
}else{
step = 1.0 / self.mutableArray_layers.count; //average step
for (int i = 0 ; i < [self.mutableArray_layers count]; i ++) {
CAShapeLayer *tmp = [self.mutableArray_layers objectAtIndex:i];
[tmp setStrokeStart:self.strokeEndValue];
if (i == (self.mutableArray_layers.count -1)) { //the last layer
self.strokeEndValue = 1.0 - self.space ;
[tmp setStrokeEnd:self.strokeEndValue];
}else {
[tmp setStrokeEnd:self.strokeEndValue + step ];
}
self.strokeEndValue += (step + self.space); // record last strokeEndValue
}
}
self.strokeEndValue = 0.0; // different os , the layoutSubViews was called different times,so,this line is resolve this problem so that figure can be shown correctly
}

Related

NSCollectionViewAttributes not getting applied correctly with custom NSFlowLayout subclass

I'm currently working on an NSCollectionView with a custom layout. For that purpose, I subclassed NSCollectionViewFlowLayout to top-align my items (which all have a fixed width what made the algorithm pretty easy). The problem I have now is that only the first nine items in my collection view get displayed correctly, that is top-aligned.
These top nine items are at least partially visible when the collection view is initially displayed. The other items that appear when scrolling down the collection view will be drawn without any top-alignment, just vertically-centered-per-row as the default behaviour of NSCollectionViewFlowLayout. By extensive logging I could verify that my Layout only provides NSCollectionViewLayoutAttributes correctly modified for top-alignment.
When I resize the window containing the NSCollectionView, thus resizing the collection view itself, all items in the visible part of it will suddenly be displayed correctly.
What am I missing?
Here comes my custom subclass:
#import "MyCollectionViewLayout.h"
#interface MyCollectionViewLayout ()
#property (nonatomic, strong) NSMutableDictionary<NSIndexPath *, NSCollectionViewLayoutAttributes *> *attributesCache;
#property NSInteger numberOfItemsPerRow;
#property NSInteger numberOfItemsTotal;
#end
#implementation MyCollectionViewLayout
- (void)prepareLayout {
NSLog(#"Preparing layout");
[super prepareLayout];
self.itemSize = CGSizeMake(100, 138);
self.minimumInteritemSpacing = 5;
self.minimumLineSpacing = 5;
self.sectionInset = NSEdgeInsetsMake(10, 10, 10, 10);
self.attributesCache = #{}.mutableCopy;
self.numberOfItemsTotal = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:0];
NSInteger numberOfItemsPerRow = 1;
CGFloat computedSize = self.itemSize.width;
CGFloat usableWidth = self.collectionView.frame.size.width - (self.sectionInset.right + self.sectionInset.left);
repeat: {
computedSize += self.minimumInteritemSpacing + self.itemSize.width;
if (computedSize < usableWidth) {
numberOfItemsPerRow++;
goto repeat;
}
}
self.numberOfItemsPerRow = numberOfItemsPerRow;
}
#pragma mark NSCollectionViewFlowLayout override
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSLog(#"Getting layout attributes for rect: %f, %f, %f, %f", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
NSArray *attributesArray = [super layoutAttributesForElementsInRect:rect].mutableCopy;
for (int i = 0; i < attributesArray.count; i++) {
NSCollectionViewLayoutAttributes *attributes = attributesArray[i];
NSLog(#"Forwarding attribute request for %#", attributes.indexPath);
attributes = [self layoutAttributesForItemAtIndexPath:attributes.indexPath];
}
return attributesArray;
}
- (NSCollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
if (!self.attributesCache[indexPath]) NSLog(#"");
NSLog(#"Getting layout attributes for %#", indexPath);
if (!self.attributesCache[indexPath]) {
NSLog(# "Not cached yet, caching full row");
[self computeAndCacheAttributesForRowContaining:indexPath];
}
return self.attributesCache[indexPath];
}
#pragma mark Private instance methods
- (void)computeAndCacheAttributesForRowContaining:(NSIndexPath *)indexPath {
NSDictionary<NSIndexPath *, NSCollectionViewLayoutAttributes *> *allAttributesInRowByPath = [self getAllAttributesInRowContaining:indexPath];
CGFloat minY = CGFLOAT_MAX;
for (NSIndexPath *path in allAttributesInRowByPath) {
if (allAttributesInRowByPath[path].frame.origin.y < minY) {
minY = allAttributesInRowByPath[path].frame.origin.y;
}
}
for (NSIndexPath *path in allAttributesInRowByPath) {
if (indexPath.item == 9) {
}
NSLog(#"Changing frame for indexPath %#", path);
NSRect frame = allAttributesInRowByPath[path].frame;
NSLog(#"Previous y Position: %f", frame.origin.y);
NSLog(#"New y Position: %f", minY);
frame.origin.y = minY;
allAttributesInRowByPath[path].frame = frame;
}
[self.attributesCache addEntriesFromDictionary:allAttributesInRowByPath];
}
- (NSDictionary<NSIndexPath *, NSCollectionViewLayoutAttributes *> *)getAllAttributesInRowContaining:(NSIndexPath *)indexPath {
NSMutableDictionary<NSIndexPath *, NSCollectionViewLayoutAttributes *> *attributesToReturn = #{}.mutableCopy;
NSInteger index = indexPath.item;
NSInteger firstIndex = index - (index % self.numberOfItemsPerRow);
NSIndexPath *path;
for (index = firstIndex; (index < firstIndex + self.numberOfItemsPerRow) && (index < self.numberOfItemsTotal); index++) {
path = [NSIndexPath indexPathForItem:index inSection:indexPath.section];
[attributesToReturn setObject:[super layoutAttributesForItemAtIndexPath:path].copy forKey:path];
}
return attributesToReturn.copy;
}
#end
You're missing attributesArray[i] = attributes; in layoutAttributesForElementsInRect : and are returning the unmodified attributes.
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSLog(#"Getting layout attributes for rect: %f, %f, %f, %f", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
NSMutableArray *attributesArray = [super layoutAttributesForElementsInRect:rect].mutableCopy;
for (int i = 0; i < attributesArray.count; i++) {
NSCollectionViewLayoutAttributes *attributes = attributesArray[i];
NSLog(#"Forwarding attribute request for %#", attributes.indexPath);
attributes = [self layoutAttributesForItemAtIndexPath:attributes.indexPath];
attributesArray[i] = attributes; // <---
}
return attributesArray;
}

How to get an array of AXMenuItems from AXMenu?

For my code I am attempting to get an array of AXMenuItems from an AXMenu (AXUIElementRef). The menu logs successfully, and here is my code:
NSArray *anArray = [NSRunningApplication runningApplicationsWithBundleIdentifier:#"com.apple.dock"];
AXUIElementRef anAXDockApp = AXUIElementCreateApplication([[anArray objectAtIndex:0] processIdentifier]);
CFTypeRef aChildren;
AXUIElementCopyAttributeValue(anAXDockApp, kAXChildrenAttribute, &aChildren);
SafeCFRelease(anAXDockApp);
CFTypeRef aMenu = CFArrayGetValueAtIndex(aChildren, 0);
NSLog(#"aMenu: %#", aMenu);
// Get menu items
CFTypeRef aMenuChildren;
AXUIElementCopyAttributeValue(aMenu, kAXVisibleChildrenAttribute, &aMenuChildren);
for (NSInteger i = 0; i < CFArrayGetCount(aMenuChildren); i++) {
AXUIElementRef aMenuItem = [self copyAXUIElementFrom:aMenu role:kAXMenuItemRole atIndex:i];
NSLog(#"aMenuItem: %#", aMenuItem); // logs (null)
CFTypeRef aTitle;
AXUIElementCopyAttributeValue(aMenuItem, kAXTitleAttribute, &aTitle);
if ([(__bridge NSString *)aTitle isEqualToString:#"New Window"] || [(__bridge NSString *)aTitle isEqualToString:#"New Finder Window"]) /* Crashes here (i can see why)*/{
AXUIElementPerformAction(aMenuItem, kAXPressAction);
[NSThread sleepForTimeInterval:1];
break;
}
}
What is the correct way to get the list of AXMenuItems?
Screenshot of the Accessibility Inspector:
I have figured out an answer, using #Willeke answer of using AXUIElementCopyElementAtPosition() to get the menu. Since there were multiple dock orientations and hiding, I had to create enums in the .h file as it would be easier to read than 0, 1, or 2.
// .h
typedef enum {
kDockPositionBottom,
kDockPositionLeft,
kDockPositionRight,
kDockPositionUnknown
} DockPosition;
typedef enum {
kDockAutohideOn,
kDockAutohideOff
} DockAutoHideState;
Then, I added the methods to get these states in the .m
// .m
- (DockPosition)dockPosition
{
NSRect screenRect = [[NSScreen mainScreen] frame];
NSRect visibleRect = [[NSScreen mainScreen] visibleFrame];
// Dont need to remove menubar height
visibleRect.origin.y = 0;
if (visibleRect.origin.x > screenRect.origin.x) {
return kDockPositionLeft;
} else if (visibleRect.size.width < screenRect.size.width) {
return kDockPositionRight;
} else if (visibleRect.size.height < screenRect.size.height) {
return kDockPositionBottom;
}
return kDockPositionUnknown;
}
- (DockAutoHideState)dockHidden
{
NSString *plistPath = [NSHomeDirectory() stringByAppendingPathComponent:#"Library/Preferences/com.apple.dock.plist"];
NSDictionary *dockDict = [NSDictionary dictionaryWithContentsOfFile:plistPath];
CFBooleanRef autohide = CFDictionaryGetValue((__bridge CFDictionaryRef)dockDict, #"autohide");
if (CFBooleanGetValue(autohide) == true) {
return kDockAutohideOn;
}
return kDockAutohideOff;
}
For the dock position unknown, I added in case it was not able to calculate it from the screen positions.
Then, I used a method to get the dock item from the menubar:
- (AXUIElementRef)getDockItemWithName:(NSString *)name
{
NSArray *anArray = [NSRunningApplication runningApplicationsWithBundleIdentifier:#"com.apple.dock"];
AXUIElementRef anAXDockApp = AXUIElementCreateApplication([[anArray objectAtIndex:0] processIdentifier]);
AXUIElementRef aList = [self copyAXUIElementFrom:anAXDockApp role:kAXListRole atIndex:0];
CFTypeRef aChildren;
AXUIElementCopyAttributeValue(aList, kAXChildrenAttribute, &aChildren);
NSInteger itemIndex = -1;
for (NSInteger i = 0; i < CFArrayGetCount(aChildren); i++) {
AXUIElementRef anElement = CFArrayGetValueAtIndex(aChildren, i);
CFTypeRef aResult;
AXUIElementCopyAttributeValue(anElement, kAXTitleAttribute, &aResult);
if ([(__bridge NSString *)aResult isEqualToString:name]) {
itemIndex = i;
}
}
SafeCFRelease(aChildren);
if (itemIndex == -1) return nil;
// We have index now do something with it
AXUIElementRef aReturnItem = [self copyAXUIElementFrom:aList role:kAXDockItemRole atIndex:itemIndex];
SafeCFRelease(aList);
return aReturnItem;
}
This SafeCFRelease() method is a very simple method that checks if the passed value is not nil, then releases (had some issues earlier).
void SafeCFRelease( CFTypeRef cf )
{
if (cf) CFRelease(cf);
}
And this method [copyAXUIElementFrom: role: atIndex:] is a method from #Willeke answer from another one of my questions:
- (AXUIElementRef)copyAXUIElementFrom:(AXUIElementRef)theContainer role:(CFStringRef)theRole atIndex:(NSInteger)theIndex {
AXUIElementRef aResultElement = NULL;
CFTypeRef aChildren;
AXError anAXError = AXUIElementCopyAttributeValue(theContainer, kAXChildrenAttribute, &aChildren);
if (anAXError == kAXErrorSuccess) {
NSUInteger anIndex = -1;
for (id anElement in (__bridge NSArray *)aChildren) {
if (theRole) {
CFTypeRef aRole;
anAXError = AXUIElementCopyAttributeValue((__bridge AXUIElementRef)anElement, kAXRoleAttribute, &aRole);
if (anAXError == kAXErrorSuccess) {
if (CFStringCompare(aRole, theRole, 0) == kCFCompareEqualTo)
anIndex++;
SafeCFRelease(aRole);
}
}
else
anIndex++;
if (anIndex == theIndex) {
aResultElement = (AXUIElementRef)CFRetain((__bridge CFTypeRef)(anElement));
break;
}
}
SafeCFRelease(aChildren);
}
return aResultElement;
}
Taking all this code, I put it into one of my methods:
// Check if in dock (otherwise cant do it)
if ([self isAppOfNameInDock:[appDict objectForKey:#"AppName"]]) {
// Get dock item
AXUIElementRef aDockItem = [self getDockItemWithName:[appDict objectForKey:#"AppName"]];
AXUIElementPerformAction(aDockItem, kAXShowMenuAction);
[NSThread sleepForTimeInterval:0.5];
CGRect aRect;
CFTypeRef aPosition;
AXUIElementCopyAttributeValue(aDockItem, kAXPositionAttribute, &aPosition);
AXValueGetValue(aPosition, kAXValueCGPointType, &aRect.origin);
SafeCFRelease(aPosition);
CFTypeRef aSize;
AXUIElementCopyAttributeValue(aDockItem, kAXSizeAttribute, &aSize);
AXValueGetValue(aSize, kAXValueCGSizeType, &aRect.size);
SafeCFRelease(aSize);
SafeCFRelease(aDockItem);
CGPoint aMenuPoint;
if ([self dockHidden] == kDockAutohideOff) {
switch ([self dockPosition]) {
case kDockPositionRight:
aMenuPoint = CGPointMake(aRect.origin.x - 18, aRect.origin.y + (aRect.size.height / 2));
break;
case kDockPositionLeft:
aMenuPoint = CGPointMake(aRect.origin.x + aRect.size.width + 18, aRect.origin.y + (aRect.size.height / 2));
break;
case kDockPositionBottom:
aMenuPoint = CGPointMake(aRect.origin.x + (aRect.size.width / 2), aRect.origin.y - 18);
break;
case kDockPositionUnknown:
aMenuPoint = CGPointMake(0, 0);
break;
}
} else {
NSRect screenFrame = [[NSScreen mainScreen] frame];
switch ([self dockPosition]) {
case kDockPositionRight:
aMenuPoint = CGPointMake(screenFrame.size.width - 18, aRect.origin.y + (aRect.size.height / 2));
break;
case kDockPositionLeft:
aMenuPoint = CGPointMake(screenFrame.origin.x + 18, aRect.origin.y + (aRect.size.height / 2));
break;
case kDockPositionBottom:
aMenuPoint = CGPointMake(aRect.origin.x + (aRect.size.width / 2), screenFrame.size.height - 18);
break;
case kDockPositionUnknown:
aMenuPoint = CGPointMake(0, 0);
break;
}
}
if ((aMenuPoint.x != 0) && (aMenuPoint.y != 0)) {
AXUIElementRef _systemWideElement = AXUIElementCreateSystemWide();
AXUIElementRef aMenu;
AXUIElementCopyElementAtPosition(_systemWideElement, aMenuPoint.x, aMenuPoint.y, &aMenu);
SafeCFRelease(_systemWideElement);
// Get menu items
CFTypeRef aMenuChildren;
AXUIElementCopyAttributeValue(aMenu, kAXVisibleChildrenAttribute, &aMenuChildren);
NSRunningApplication *app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:[appDict objectForKey:#"BundleID"]] objectAtIndex:0];
for (NSInteger i = 0; i < CFArrayGetCount(aMenuChildren); i++) {
AXUIElementRef aMenuItem = [self copyAXUIElementFrom:aMenu role:kAXMenuItemRole atIndex:i];
CFTypeRef aTitle;
AXUIElementCopyAttributeValue(aMenuItem, kAXTitleAttribute, &aTitle);
// Supports chrome, safari, and finder
if ([(__bridge NSString *)aTitle isEqualToString:#"New Window"] || [(__bridge NSString *)aTitle isEqualToString:#"New Finder Window"]) {
AXUIElementPerformAction(aMenuItem, kAXPressAction);
NSInteger numberOfWindows = [self numberOfWindowsOpenFromApplicationWithPID:[app processIdentifier]];
// Wait until open
while ([self numberOfWindowsOpenFromApplicationWithPID:[app processIdentifier]] <= numberOfWindows) {
}
break;
}
}
SafeCFRelease(aMenu);
SafeCFRelease(aMenuChildren);
}
}
This is pretty complicated, but it works. I probably can't explain it, but I have stress tested this code and it works quite well.
Answer to the question "How to get an array of AXMenuItems from AXMenu?": aMenuChildren is the list of menu items. To be sure you could filter by role kAXMenuItemRole.
Answer to the question "Why does it not work?": The menu isn't a menu. When you inspect the menu in Axccessibility Inspector, it displays a warning "Parent does not report element as one of its children". The Dock app has one child, a list of dock items.

Vfr-Reader UIScrollView from left to right

I'm using the Vfr-Reader source code and here I'm not able to scroll the scrollview from left to right, and by default it is from right to left. How can I achieve this I tried many just setting the offset and also tried putting the condition with the origin.
- (void)showDocumentPage:(NSInteger)page
{
if (page != currentPage) // Only if different
{
NSInteger minValue; NSInteger maxValue;
NSInteger maxPage = [document.pageCount integerValue];
NSInteger minPage = 1;
if ((page < minPage) || (page > maxPage)) return;
if (maxPage <= PAGING_VIEWS) // Few pages
{
minValue = minPage;
maxValue = maxPage;
}
else // Handle more pages
{
minValue = (page - 1);
maxValue = (page + 1);
if (minValue < minPage)
{minValue++; maxValue++;}
else
if (maxValue > maxPage)
{minValue--; maxValue--;}
}
NSMutableIndexSet *newPageSet = [NSMutableIndexSet new];
NSMutableDictionary *unusedViews = [contentViews mutableCopy];
CGRect viewRect = CGRectZero; viewRect.size = theScrollView.bounds.size;
for (NSInteger number = minValue; number <= maxValue; number++)
{
NSNumber *key = [NSNumber numberWithInteger:number]; // # key
ReaderContentView *contentView = [contentViews objectForKey:key];
if (contentView == nil) // Create a brand new document content view
{
NSURL *fileURL = document.fileURL; NSString *phrase = document.password; // Document properties
contentView = [[ReaderContentView alloc] initWithFrame:viewRect fileURL:fileURL page:number password:phrase];
[theScrollView addSubview:contentView]; [contentViews setObject:contentView forKey:key];
contentView.message = self; [newPageSet addIndex:number];
}
else // Reposition the existing content view
{
contentView.frame = viewRect; [contentView zoomReset];
[unusedViews removeObjectForKey:key];
}
viewRect.origin.x += viewRect.size.width;
}
[unusedViews enumerateKeysAndObjectsUsingBlock: // Remove unused views
^(id key, id object, BOOL *stop)
{
[contentViews removeObjectForKey:key];
ReaderContentView *contentView = object;
[contentView removeFromSuperview];
}
];
unusedViews = nil; // Release unused views
CGFloat viewWidthX1 = viewRect.size.width;
CGFloat viewWidthX2 = (viewWidthX1 * 2.0f);
CGPoint contentOffset = CGPointZero;
if (maxPage >= PAGING_VIEWS)
{
if (page == maxPage)
contentOffset.x = viewWidthX2;
else
if (page != minPage)
contentOffset.x = viewWidthX1;
}
else
if (page == (PAGING_VIEWS - 1))
contentOffset.x = viewWidthX1;
if (CGPointEqualToPoint(theScrollView.contentOffset, contentOffset) == false)
{
theScrollView.contentOffset = contentOffset; // Update content offset
}
if ([document.pageNumber integerValue] != page) // Only if different
{
document.pageNumber = [NSNumber numberWithInteger:page]; // Update page number
}
NSURL *fileURL = document.fileURL; NSString *phrase = document.password; NSString *guid = document.guid;
if ([newPageSet containsIndex:page] == YES) // Preview visible page first
{
NSNumber *key = [NSNumber numberWithInteger:page]; // # key
ReaderContentView *targetView = [contentViews objectForKey:key];
[targetView showPageThumb:fileURL page:page password:phrase guid:guid];
[newPageSet removeIndex:page]; // Remove visible page from set
}
[newPageSet enumerateIndexesWithOptions:NSEnumerationReverse usingBlock: // Show previews
^(NSUInteger number, BOOL *stop)
{
NSNumber *key = [NSNumber numberWithInteger:number]; // # key
ReaderContentView *targetView = [contentViews objectForKey:key];
[targetView showPageThumb:fileURL page:number password:phrase guid:guid];
}
];
newPageSet = nil; // Release new page set
[mainPagebar updatePagebar]; // Update the pagebar display
[self updateToolbarBookmarkIcon]; // Update bookmark
currentPage = page; // Track current page number
}
}
I changed this
if (maxPage <= PAGING_VIEWS) // Few pages
{
minValue = minPage;
maxValue = maxPage;
}
else // Handle more pages
{
minValue = (page - 1);
maxValue = (page + 1);
if (minValue < minPage)
{minValue++; maxValue++;}
else
if (maxValue > maxPage)
{minValue--; maxValue--;}
}
</pre>
I got the correct minValue and max value to load the left page and right page once you are on the current page, for example if I'm on the 3 page, then on the left side it should be 4 and right side should be 2 but it's not loading these instead it loads left side 2 and right side 5. Something that I'm missing can anyone help me out.

translationInView not working

I used this code many times but in my current project it isn't working. Here is the error (third line):
"No visible #interface for 'UIGestureRecognizer' declares the selector 'translationInView:'
and my simple code:
- (IBAction)panLayer:(UIGestureRecognizer *)pan{
if (pan.state == UIGestureRecognizerStateChanged) {
CGPoint point = [pan translationInView:self.view];
CGRect frame = self.settingsView.frame;
frame.origin.y = self.layerPosition + point.y;
if (frame.origin.y < 0) {
frame.origin.y = 0;
}
}
You are looking for the UIPanGestureRecognizer that has UIGestureRecognizer as the parent class.
- (IBAction)panLayer:(UIPanGestureRecognizer *)pan{
}

How do I implement scrollWheel elastic scrolling on OSX

I am trying to create a custom view, which is similar to NSScrollView, but is based on CoreAnimation CAScrollLayer/CATiledLayer. Basically, my app requires a lot of near realtime CGPath drawing, and animates these paths using shape layers (it's similar to the way GarageBand animates while recording). The first prototype I created used NSScrollView, but I could not get more than 20 frames per second with it (The reason was that NSRulerView updates by drawing every time the scrollEvent happens, and the entire call flow from -[NSClipView scrollToPoint] to -[NSScrollView reflectScrolledClipView:] is extremely expensive and inefficient).
I've created a custom view that uses CAScrollLayer as scrolling mechanism and CATiledLayer as the traditional documentView (for infinite scrolling option), and now I can get close to 60fps. However, I'm having hard time implementing scrollWheel elastic scrolling, and I'm not sure how to do it. Here's the code I have so far, and would really appreciate if someone can tell me how to implement elasticScrolling.
-(void) scrollWheel:(NSEvent *)theEvent{
NSCAssert(mDocumentScrollLayer, #"The Scroll Layer Cannot be nil");
NSCAssert(mDocumentLayer, #"The tiled layer cannot be nil");
NSCAssert(self.layer, #"The base layer of view cannot be nil");
NSCAssert(mRulerLayer, #"The ScrollLayer for ruler cannot be nil");
NSPoint locationInWindow = [theEvent locationInWindow];
NSPoint locationInBaseLayer = [self convertPoint:locationInWindow
fromView:nil];
NSPoint locationInRuler = [mRulerLayer convertPoint:locationInBaseLayer
fromLayer:self.layer];
if ([mRulerLayer containsPoint:locationInRuler]) {
return;
}
CGRect docRect = [mDocumentScrollLayer convertRect:[mDocumentLayer bounds]
fromLayer:mDocumentLayer];
CGRect scrollRect = [mDocumentScrollLayer visibleRect];
CGPoint newOrigin = scrollRect.origin;
CGFloat deltaX = [theEvent scrollingDeltaX];
CGFloat deltaY = [theEvent scrollingDeltaY];
if ([self isFlipped]) {
deltaY *= -1;
}
scrollRect.origin.x -= deltaX;
scrollRect.origin.y += deltaY;
if ((NSMinX(scrollRect) < NSMinX(docRect)) ||
(NSMaxX(scrollRect) > NSMaxX(docRect)) ||
(NSMinY(scrollRect) < NSMinY(docRect)) ||
(NSMaxY(scrollRect) > NSMaxX(docRect))) {
mIsScrollingPastEdge = YES;
CGFloat heightPhase = 0.0;
CGFloat widthPhase = 0.0;
CGSize size = [self frame].size;
if (NSMinX(scrollRect) < NSMinX(docRect)) {
widthPhase = ABS(NSMinX(scrollRect) - NSMinX(docRect));
}
if (NSMaxX(scrollRect) > NSMaxX(docRect)) {
widthPhase = ABS(NSMaxX(scrollRect) - NSMaxX(docRect));
}
if (NSMinY(scrollRect) < NSMinY(docRect)) {
heightPhase = ABS(NSMinY(scrollRect) - NSMinY(docRect));
}
if (NSMaxY(scrollRect) > NSMaxY(docRect)) {
heightPhase = ABS(NSMaxY(scrollRect) - NSMaxY(docRect));
}
if (widthPhase > size.width/2.0) {
widthPhase = size.width/2.0;
}
if (heightPhase > size.width/2.0) {
heightPhase = size.width/2.0;
}
deltaX = deltaX*(1-(2*widthPhase/size.width));
deltaY = deltaY*(1-(2*heightPhase/size.height));
}
newOrigin.x -= deltaX;
newOrigin.y += deltaY;
if ( mIsScrollingPastEdge &&
(([theEvent phase] == NSEventPhaseEnded) ||
([theEvent momentumPhase] == NSEventPhaseEnded)
)
){
CGPoint confinedScrollPoint = [mDocumentScrollLayer bounds].origin;
mIsScrollingPastEdge = NO;
CGRect visibleRect = [mDocumentScrollLayer visibleRect];
if (NSMinX(scrollRect) < NSMinX(docRect)){
confinedScrollPoint.x = docRect.origin.x;
}
if(NSMinY(scrollRect) < NSMinY(docRect)) {
confinedScrollPoint.y = docRect.origin.y;
}
if (NSMaxX(scrollRect) > NSMaxX(docRect)) {
confinedScrollPoint.x = NSMaxX(docRect) - visibleRect.size.width;
}
if (NSMaxY(scrollRect) > NSMaxY(docRect)){
confinedScrollPoint.y = NSMaxY(docRect) - visibleRect.size.height;
}
[mDocumentScrollLayer scrollToPoint:confinedScrollPoint];
CGPoint rulerPoint = [mRulerLayer bounds].origin;
rulerPoint.x = [mDocumentLayer bounds].origin.x;
[mRulerLayer scrollToPoint:rulerPoint];
return;
}
CGPoint rulerPoint = [mDocumentScrollLayer convertPoint:newOrigin
toLayer:mRulerLayer];
rulerPoint.y = [mRulerLayer bounds].origin.y;
if (!mIsScrollingPastEdge) {
[CATransaction setDisableActions:YES];
[mDocumentScrollLayer scrollToPoint:newOrigin];
[CATransaction commit];
}else{
[mDocumentScrollLayer scrollToPoint:newOrigin];
[mRulerLayer scrollToPoint:rulerPoint];
}
}
Take a look at TUIScrollView at https://github.com/twitter/twui.
The Core concept of adding ElasticScrolling is to use a SpringSolver.
This has a ElasticScrollView that you can reuse:
https://github.com/eonist/Element

Resources