added new ASLayoutableInspectorNode features

This commit is contained in:
Hannah Troisi
2016-03-25 15:47:12 -07:00
parent 9f8d7e952c
commit 2d4ec243ae
14 changed files with 532 additions and 112 deletions

View File

@@ -9,6 +9,9 @@
#import "ASControlNode.h"
#import "ASControlNode+Subclasses.h"
#import "ASThread.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASLayoutSpec+Debug.h"
#import "ASLayoutableInspectorNode.h"
// UIControl allows dragging some distance outside of the control itself during
// tracking. This value depends on the device idiom (25 or 70 points), so
@@ -87,9 +90,26 @@ static BOOL _enableHitTestDebug = NO;
// As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on.
self.userInteractionEnabled = NO;
return self;
}
- (void)inspectElement
{
[ASLayoutableInspectorNode sharedInstance].layoutableToEdit = self;
}
- (void)setHierarchyState:(ASHierarchyState)hierarchyState
{
[super setHierarchyState:hierarchyState];
// FIXME: handle disabling hierarchy state on nodes that had previously enabled it (remove target)
if (ASHierarchyStateIncludesVisualizeLayoutSpecs(hierarchyState)) {
[self addTarget:self action:@selector(inspectElement) forControlEvents:ASControlNodeEventTouchUpInside];
// NSLog(@"%@", self);
}
}
- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled
{
[super setUserInteractionEnabled:userInteractionEnabled];
@@ -236,6 +256,7 @@ static BOOL _enableHitTestDebug = NO;
{
NSParameterAssert(action);
NSParameterAssert(controlEventMask != 0);
ASDisplayNodeAssert(!self.isLayerBacked, @"ASControlNode is layer backed, will never be able to call target in target:action: pair.");
ASDN::MutexLocker l(_controlLock);

View File

@@ -1838,6 +1838,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
[ASLayoutSpec setShouldVisualizeLayoutSpecs2:YES];
}
ASStaticLayoutSpec *staticSpec = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[[self layoutSpecThatFits:constrainedSize]]];
ASLayoutSpec *layoutSpec = staticSpec;

View File

@@ -33,10 +33,11 @@ static BOOL __shouldVisualizeLayoutSpecs = NO;
{
self = [super init];
if (self) {
self.layer.borderWidth = 2;
self.layoutSpec = layoutSpec;
self.usesImplicitHierarchyManagement = YES;
self.layer.borderColor = [[UIColor redColor] CGColor];
self.layer.borderWidth = 2;
[self addTarget:self action:@selector(layoutMagicNodeTapped:) forControlEvents:ASControlNodeEventTouchUpInside];
}
return self;
@@ -47,16 +48,35 @@ static BOOL __shouldVisualizeLayoutSpecs = NO;
ASInsetLayoutSpec *insetSpec = [[ASInsetLayoutSpec alloc] init]; // FIXME: need to auto pass properties to children
insetSpec.neverShouldVisualize = YES;
self.layoutSpec.neverShouldVisualize = YES;
UIEdgeInsets insets = UIEdgeInsetsZero; //UIEdgeInsetsMake(10, 10, 10, 10);
CGFloat insetFloat = [ASLayoutableInspectorNode sharedInstance].vizNodeInsetSize;
UIEdgeInsets insets = UIEdgeInsetsMake(insetFloat, insetFloat, insetFloat, insetFloat);
// UIEdgeInsets insets = UIEdgeInsetsZero;
// propogate child's layoutSpec properties to the inset that we are adding
insetSpec.flexGrow = _layoutSpec.flexGrow;
insetSpec.flexGrow = _layoutSpec.flexGrow; // FIXME:
insetSpec.flexShrink = _layoutSpec.flexShrink;
insetSpec.alignSelf = _layoutSpec.alignSelf;
insetSpec.alignSelf = _layoutSpec.alignSelf;
insetSpec.insets = insets;
insetSpec.child = self.layoutSpec;
insetSpec.insets = insets;
insetSpec.child = self.layoutSpec;
return self.layoutSpec;
return insetSpec; //self.layoutSpec;
}
- (void)setLayoutSpec:(ASLayoutSpec *)layoutSpec
{
_layoutSpec = layoutSpec;
// self.flexGrow = _layoutSpec.flexGrow;
// self.flexShrink = _layoutSpec.flexShrink;
// self.alignSelf = _layoutSpec.alignSelf;
if ([layoutSpec isKindOfClass:[ASInsetLayoutSpec class]]) {
self.layer.borderColor = [[UIColor redColor] CGColor];
} else if ([layoutSpec isKindOfClass:[ASStackLayoutSpec class]]) {
self.layer.borderColor = [[UIColor greenColor] CGColor];
}
}
- (void)layoutMagicNodeTapped:(UIGestureRecognizer *)sender
@@ -64,5 +84,10 @@ static BOOL __shouldVisualizeLayoutSpecs = NO;
[[ASLayoutableInspectorNode sharedInstance] setLayoutableToEdit:self.layoutSpec];
}
- (NSString *)description
{
return [self.layoutSpec description]; // FIXME: expand on layoutSpec description (e.g. have StackLayoutSpec return horz/vert)
}
@end

View File

@@ -11,7 +11,7 @@
@protocol ASLayoutableInspectorNodeDelegate <NSObject>
- (void)shouldShowMasterSplitViewController;
- (void)toggleVizualization:(BOOL)toggle;
@end
@@ -19,6 +19,7 @@
@property (nonatomic, strong) id<ASLayoutable> layoutableToEdit;
@property (nonatomic, strong) id<ASLayoutableInspectorNodeDelegate> delegate;
@property (nonatomic, assign) CGFloat vizNodeInsetSize;
+ (instancetype)sharedInstance;

View File

@@ -19,6 +19,8 @@
// Navigate layout hierarchy
ASButtonNode *_parentNodeNavBtn;
ASButtonNode *_siblingNodeRightNavBtn;
ASButtonNode *_addNodeNavBtn;
ASButtonNode *_addLayoutSpecNavBtn;
ASButtonNode *_siblingNodeLefttNavBtn;
ASButtonNode *_childNodeNavBtn;
@@ -48,7 +50,12 @@
// LayoutSpec properties
ASTextNode *_layoutSpecPropertiesSectionTitle;
// debug help
ASTextNode *_debugSectionTitle;
ASTextNode *_vizNodeInsetSizeTitle;
ASButtonNode *_vizNodeInsetSizeBtn;
ASButtonNode *_vizNodeBordersBtn;
}
#pragma mark - class methods
@@ -72,6 +79,8 @@
self.usesImplicitHierarchyManagement = YES;
_vizNodeInsetSize = 0;
_itemDescription = [[ASTextNode alloc] init];
_itemPropertiesSectionTitle = [[ASTextNode alloc] init];
@@ -80,6 +89,10 @@
_layoutablePropertiesSectionTitle.attributedString = [self attributedStringFromString:@"<Layoutable> Properties"];
_layoutSpecPropertiesSectionTitle = [[ASTextNode alloc] init];
_layoutSpecPropertiesSectionTitle.attributedString = [self attributedStringFromString:@"<LayoutSpec> Properties"];
_debugSectionTitle = [[ASTextNode alloc] init];
_debugSectionTitle.attributedString = [self attributedStringFromString:@"debugging help"];
_vizNodeInsetSizeTitle = [[ASTextNode alloc] init];
_vizNodeInsetSizeTitle.attributedString = [self attributedStringFromString:@"inset ASLayoutSpecs"];
_flexGrowBtn = [self makeBtnNodeWithTitle:@"flexGrow"];
[_flexGrowBtn addTarget:self action:@selector(setFlexGrowValue:) forControlEvents:ASControlNodeEventTouchUpInside];
@@ -101,11 +114,28 @@
_itemBackgroundColorBtn = [self makeBtnNodeWithTitle:@"node color"];
[_itemBackgroundColorBtn addTarget:self action:@selector(changeColor:) forControlEvents:ASControlNodeEventTouchUpInside];
_vizNodeBordersBtn = [self makeBtnNodeWithTitle:@"visualize ASLayoutSpecs"];
[_vizNodeBordersBtn addTarget:self action:@selector(setVizNodeBorders:) forControlEvents:ASControlNodeEventTouchUpInside];
_vizNodeBordersBtn.selected = YES;
_vizNodeInsetSizeBtn = [self makeBtnNodeWithTitle:@"overlap ASLayoutSpecs"];
[_vizNodeInsetSizeBtn addTarget:self action:@selector(setVizNodeInsets:) forControlEvents:ASControlNodeEventTouchUpInside];
_vizNodeInsetSizeBtn.selected = YES;
_parentNodeNavBtn = [self makeBtnNodeWithTitle:@"parent\nnode"];
_siblingNodeRightNavBtn = [self makeBtnNodeWithTitle:@"sibling\nnode"];
_addNodeNavBtn = [self makeBtnNodeWithTitle:@"add\nnode"];
_addLayoutSpecNavBtn = [self makeBtnNodeWithTitle:@"add\nlayoutSpec"];
_siblingNodeLefttNavBtn = [self makeBtnNodeWithTitle:@"sibling\nnode"];
_childNodeNavBtn = [self makeBtnNodeWithTitle:@"child\nnode"];
// _parentNodeNavBtn.selected = YES;
// _siblingNodeRightNavBtn.selected = YES;
// _addNodeNavBtn.selected = YES;
// _addLayoutSpecNavBtn.selected = YES;
// _siblingNodeLefttNavBtn.selected = YES;
// _childNodeNavBtn.selected = YES;
_slider = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{
UISlider *slider = [[UISlider alloc] init];
return slider;
@@ -131,16 +161,21 @@
{
// navigate layout hierarchy
_parentNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter;
_childNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter;
ASStackLayoutSpec *horizontalStackNav = [ASStackLayoutSpec horizontalStackLayoutSpec];
horizontalStackNav.flexGrow = YES;
horizontalStackNav.alignSelf = ASStackLayoutAlignSelfCenter;
horizontalStackNav.children = @[_siblingNodeLefttNavBtn, _siblingNodeRightNavBtn];
ASStackLayoutSpec *horizontalStack = [ASStackLayoutSpec horizontalStackLayoutSpec];
horizontalStack.flexGrow = YES;
ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
spacer.flexGrow = YES;
horizontalStack.children = @[_flexGrowBtn, spacer];
_flexGrowValue.alignSelf = ASStackLayoutAlignSelfEnd; // FIXME: framework give a warning if you use ASAlignmentBottom!!!!!
_flexGrowValue.alignSelf = ASStackLayoutAlignSelfEnd; // FIXME: make framework give a warning if you use ASAlignmentBottom!!!!!
ASStackLayoutSpec *horizontalStack2 = [ASStackLayoutSpec horizontalStackLayoutSpec];
horizontalStack2.flexGrow = YES;
@@ -153,7 +188,7 @@
_flexBasisValue.alignSelf = ASStackLayoutAlignSelfEnd;
ASStackLayoutSpec *itemDescriptionStack = [ASStackLayoutSpec verticalStackLayoutSpec];
itemDescriptionStack.children = @[_itemDescription, _itemBackgroundColorBtn,];
itemDescriptionStack.children = @[_itemDescription];
itemDescriptionStack.spacing = 5;
itemDescriptionStack.flexGrow = YES;
@@ -166,11 +201,16 @@
layoutSpecStack.children = @[_layoutSpecPropertiesSectionTitle, _alignItemsBtn];
layoutSpecStack.spacing = 5;
layoutSpecStack.flexGrow = YES;
ASStackLayoutSpec *debugHelpStack = [ASStackLayoutSpec verticalStackLayoutSpec];
debugHelpStack.children = @[_debugSectionTitle, _vizNodeInsetSizeBtn, _vizNodeBordersBtn];
debugHelpStack.spacing = 5;
debugHelpStack.flexGrow = YES;
ASStackLayoutSpec *verticalLayoutableStack = [ASStackLayoutSpec verticalStackLayoutSpec];
verticalLayoutableStack.flexGrow = YES;
verticalLayoutableStack.spacing = 20;
verticalLayoutableStack.children = @[_parentNodeNavBtn, horizontalStackNav, _childNodeNavBtn, itemDescriptionStack, layoutableStack, layoutSpecStack];
verticalLayoutableStack.children = @[_parentNodeNavBtn, horizontalStackNav, _childNodeNavBtn, itemDescriptionStack, layoutableStack, layoutSpecStack, debugHelpStack];
verticalLayoutableStack.alignItems = ASStackLayoutAlignItemsStretch; // stretch headerStack to fill horizontal space
ASLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(100, 10, 10, 10) child:verticalLayoutableStack];
@@ -202,28 +242,18 @@
// _flexBasisBtn.selected = self.layoutableToEdit.flexShrink;
// _flexBasisValue.attributedString = [self attributedStringFromString: (_flexBasisBtn.selected) ? @"YES" : @"NO"];
NSUInteger alignSelfValue = [self.layoutableToEdit alignSelf];
_alignSelfBtn.selected = alignSelfValue ? YES : NO;
NSString *newTitle = [@"alignSelf:" stringByAppendingString:[self alignSelfName:alignSelfValue]];
[_alignSelfBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal];
// _alignItemsBtn.selected = YES;
// if ([[self layoutSpec] isKindOfClass:[ASStackLayoutSpec class]]) {
// NSUInteger alignItemsValue = [(ASStackLayoutSpec *)[self layoutSpec] alignItems];
// newTitle = [@"alignItems:" stringByAppendingString:[self typeDisplayNameItems:alignItemsValue]];
if ([self layoutSpec]) {
_alignItemsBtn.enabled = YES;
// NSUInteger alignItemsValue = [[self layoutSpec] alignItems];
// newTitle = [@"alignItems:" stringByAppendingString:[self alignSelfName:alignItemsValue]];
// [_alignItemsBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal];
// }
//
// if ([layoutable isKindOfClass:[ASLayoutSpec class]]) {
// return [self attributedStringFromString:[(ASLayoutSpec *)layoutable asciiArtString]];
// } else if ([layoutable isKindOfClass:[ASDisplayNode class]]) {
// return [self attributedStringFromString:[(ASControlNode *)layoutable asciiArtString]];
// }
}
[self setNeedsLayout];
}
@@ -261,7 +291,7 @@
_alignSelfBtn.enabled = NO;
_spacingBeforeBtn.enabled = NO;
_spacingAfterBtn.enabled = NO;
_alignItemsBtn.enabled = NO;
_alignItemsBtn.enabled = YES;
}
}
@@ -378,6 +408,34 @@
[self updateInspectorWithLayoutable];
}
- (void)setVizNodeInsets:(ASButtonNode *)sender
{
BOOL newState = !sender.selected;
if (newState == YES) {
self.vizNodeInsetSize = 0;
[self.delegate toggleVizualization:NO]; // FIXME
[self.delegate toggleVizualization:YES]; // FIXME
_vizNodeBordersBtn.selected = YES;
} else {
self.vizNodeInsetSize = 10;
[self.delegate toggleVizualization:NO]; // FIXME
[self.delegate toggleVizualization:YES]; // FIXME
}
sender.selected = newState;
}
- (void)setVizNodeBorders:(ASButtonNode *)sender
{
BOOL newState = !sender.selected;
[self.delegate toggleVizualization:newState]; // FIXME
sender.selected = newState;
}
//

View File

@@ -129,7 +129,7 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey";
{
if ([child isKindOfClass:[ASLayoutSpec class]]) {
[(ASLayoutSpec *)child setShouldVisualize:self.shouldVisualize];
NSLog(@"%@ %@ %d", self, child, self.shouldVisualize);
// NSLog(@"%@ %@ %d", self, child, self.shouldVisualize);
}
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
self.layoutChildren[identifier] = [self layoutableToAddFromLayoutable:child];
@@ -143,7 +143,7 @@ static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey";
for (id<ASLayoutable> child in children) {
if ([child isKindOfClass:[ASLayoutSpec class]]) {
[(ASLayoutSpec *)child setShouldVisualize:self.shouldVisualize];
NSLog(@"%@ %@ %d", self, child, self.shouldVisualize);
// NSLog(@"%@ %@ %d", self, child, self.shouldVisualize);
}
[finalChildren addObject:[self layoutableToAddFromLayoutable:child]];
}

View File

@@ -12,6 +12,8 @@
3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */; };
6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; };
6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; };
7602C7651CA4F83100D0D917 /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 7602C7641CA4F83100D0D917 /* Utilities.m */; };
7602C7671CA4FB5300D0D917 /* resizeHandle.png in Resources */ = {isa = PBXBuildFile; fileRef = 7602C7661CA4FB5300D0D917 /* resizeHandle.png */; };
76466F321C9DFFC4006C4D2D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76466F2B1C9DFFC4006C4D2D /* AppDelegate.m */; };
76466F331C9DFFC4006C4D2D /* ColorNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 76466F2D1C9DFFC4006C4D2D /* ColorNode.m */; };
76466F341C9DFFC4006C4D2D /* PlaygroundNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 76466F2F1C9DFFC4006C4D2D /* PlaygroundNode.m */; };
@@ -29,6 +31,9 @@
3D24B17D1E4A4E7A9566C5E9 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; };
6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; };
7602C7631CA4F83100D0D917 /* Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = "<group>"; };
7602C7641CA4F83100D0D917 /* Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Utilities.m; sourceTree = "<group>"; };
7602C7661CA4FB5300D0D917 /* resizeHandle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = resizeHandle.png; sourceTree = "<group>"; };
76466F2A1C9DFFC4006C4D2D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
76466F2B1C9DFFC4006C4D2D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
76466F2C1C9DFFC4006C4D2D /* ColorNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ColorNode.h; sourceTree = "<group>"; };
@@ -92,6 +97,8 @@
76466F2F1C9DFFC4006C4D2D /* PlaygroundNode.m */,
76466F2C1C9DFFC4006C4D2D /* ColorNode.h */,
76466F2D1C9DFFC4006C4D2D /* ColorNode.m */,
7602C7631CA4F83100D0D917 /* Utilities.h */,
7602C7641CA4F83100D0D917 /* Utilities.m */,
05E2128419D4DB510098F589 /* Supporting Files */,
);
path = Sample;
@@ -102,6 +109,7 @@
children = (
0585427F19D4DBE100606EA6 /* Default-568h@2x.png */,
6C2C82AA19EE274300767484 /* Default-667h@2x.png */,
7602C7661CA4FB5300D0D917 /* resizeHandle.png */,
6C2C82AB19EE274300767484 /* Default-736h@3x.png */,
05E2128519D4DB510098F589 /* Info.plist */,
05E2128619D4DB510098F589 /* main.m */,
@@ -189,6 +197,7 @@
0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */,
6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */,
6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */,
7602C7671CA4FB5300D0D917 /* resizeHandle.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -249,6 +258,7 @@
files = (
76466F321C9DFFC4006C4D2D /* AppDelegate.m in Sources */,
05E2128719D4DB510098F589 /* main.m in Sources */,
7602C7651CA4F83100D0D917 /* Utilities.m in Sources */,
76466F331C9DFFC4006C4D2D /* ColorNode.m in Sources */,
76466F341C9DFFC4006C4D2D /* PlaygroundNode.m in Sources */,
76F58D5C1C9E15C1004512CC /* PlaygroundContainerNode.m in Sources */,

View File

@@ -43,6 +43,7 @@
splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
splitViewController.viewControllers = [NSArray arrayWithObjects:masterNav, detailNav, nil];
splitViewController.delegate = detailViewController;
splitViewController.maximumPrimaryColumnWidth = 200;
detailViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem;

View File

@@ -14,7 +14,7 @@
@implementation PlaygroundContainerNode
{
PlaygroundNode *_playgroundNode;
ASDisplayNode *_resizeHandle;
ASImageNode *_resizeHandle;
}
- (instancetype)init
@@ -28,8 +28,9 @@
_playgroundNode = [[PlaygroundNode alloc] init];
_resizeHandle = [[ASDisplayNode alloc] init];
_resizeHandle.backgroundColor = [UIColor greenColor];
_resizeHandle = [[ASImageNode alloc] init];
_resizeHandle.image = [UIImage imageNamed:@"resizeHandle"];
_resizeHandle.userInteractionEnabled = YES;
[self.view addSubnode:_resizeHandle];
UILongPressGestureRecognizer *lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(resizePlayground:)];
@@ -43,7 +44,7 @@
return self;
}
#define RESIZE_HANDLE_SIZE 10
#define RESIZE_HANDLE_SIZE 30
- (void)layout
{
[super layout];
@@ -62,7 +63,8 @@
_playgroundNode.flexGrow = YES;
_playgroundNode.flexShrink = YES;
return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[_playgroundNode]];
UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10);
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:_playgroundNode];
}
- (void)resizePlayground:(UIGestureRecognizer *)sender

View File

@@ -10,15 +10,24 @@
#import "ColorNode.h"
#import "AsyncDisplayKit+Debug.h"
#import "ASLayoutableInspectorNode.h"
#import "Utilities.h"
#define USER_IMAGE_HEIGHT 60
#define HORIZONTAL_BUFFER 10
#define VERTICAL_BUFFER 5
#define FONT_SIZE 20
@implementation PlaygroundNode
{
NSArray *_colorNodes;
ASDisplayNode *_individualColorNode;
ASTextNode *_textNode1;
ASTextNode *_textNode2;
ASTextNode *_textNode3;
ASNetworkImageNode *_userAvatarImageView;
ASNetworkImageNode *_photoImageView;
ASTextNode *_userNameLabel;
ASTextNode *_photoLocationLabel;
ASTextNode *_photoTimeIntervalSincePostLabel;
ASTextNode *_photoLikesLabel;
ASTextNode *_photoDescriptionLabel;
}
#pragma mark - Lifecycle
- (instancetype)init
@@ -26,95 +35,153 @@
self = [super init];
if (self) {
self.backgroundColor = [UIColor whiteColor];
self.usesImplicitHierarchyManagement = YES;
// self.clipsToBounds = YES; // make outside bounds semi-transparent
ColorNode *node = [[ColorNode alloc] init];
ColorNode *node2 = [[ColorNode alloc] init];
ColorNode *node3 = [[ColorNode alloc] init];
_colorNodes = @[node, node2, node3];
_individualColorNode = [[ColorNode alloc] init];
_individualColorNode.backgroundColor = [UIColor orangeColor];
_userAvatarImageView = [[ASNetworkImageNode alloc] init];
_userAvatarImageView.URL = [NSURL URLWithString:@"https://s-media-cache-ak0.pinimg.com/avatars/503h_1458880322_140.jpg"];
// user interaction off by default
_textNode1 = [[ASTextNode alloc] init];
_textNode1.attributedString = [[NSAttributedString alloc] initWithString:@"test"];
_textNode1.backgroundColor = [UIColor greenColor];
_textNode1.userInteractionEnabled = YES;
[_textNode1 addTarget:self action:@selector(textTapped:) forControlEvents:ASControlNodeEventTouchUpInside];
// FIXME: autocomplete for this line seems broken
[_userAvatarImageView setImageModificationBlock:^UIImage *(UIImage *image) {
CGSize profileImageSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT);
return [image makeCircularImageWithSize:profileImageSize];
}];
_textNode2 = [[ASTextNode alloc] init];
_textNode2.attributedString = [[NSAttributedString alloc] initWithString:@"Hhhhhhhhhheeeeeeeeeelllllloooooooo"];
_textNode2.backgroundColor = [UIColor greenColor];
_textNode2.userInteractionEnabled = YES;
[_textNode2 addTarget:self action:@selector(textTapped:) forControlEvents:ASControlNodeEventTouchUpInside];
_userNameLabel = [[ASTextNode alloc] init];
_userNameLabel.attributedString = [self usernameAttributedStringWithFontSize:FONT_SIZE];
_textNode3 = [[ASTextNode alloc] init];
_textNode3.attributedString = [[NSAttributedString alloc] initWithString:@"another test text node"];
_textNode3.backgroundColor = [UIColor greenColor];
_textNode3.userInteractionEnabled = YES;
[_textNode3 addTarget:self action:@selector(textTapped:) forControlEvents:ASControlNodeEventTouchUpInside];
_photoLocationLabel = [[ASTextNode alloc] init];
_photoLocationLabel.maximumNumberOfLines = 1;
_photoLocationLabel.attributedString = [self locationAttributedStringWithFontSize:FONT_SIZE];
_photoTimeIntervalSincePostLabel = [[ASTextNode alloc] init];
_photoTimeIntervalSincePostLabel.attributedString = [self uploadDateAttributedStringWithFontSize:FONT_SIZE];
_photoImageView = [[ASNetworkImageNode alloc] init];
_photoImageView.URL = [NSURL URLWithString:@"https://s-media-cache-ak0.pinimg.com/564x/9f/5b/3a/9f5b3a35640bc7a5d484b66124c48c46.jpg"];
_photoLikesLabel = [[ASTextNode alloc] init];
_photoLikesLabel.attributedString = [self likesAttributedStringWithFontSize:FONT_SIZE];
_photoDescriptionLabel = [[ASTextNode alloc] init];
_photoDescriptionLabel.attributedString = [self descriptionAttributedStringWithFontSize:FONT_SIZE];
_photoDescriptionLabel.maximumNumberOfLines = 3;
}
return self;
}
- (void)textTapped:(UIGestureRecognizer *)sender
{
[ASLayoutableInspectorNode sharedInstance].layoutableToEdit = (ASTextNode *)sender;
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
NSMutableArray *children = [[NSMutableArray alloc] init];
for (ASDisplayNode *node in _colorNodes) {
UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10);
node.flexGrow = YES;
ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:node];
insetSpec.flexGrow = YES;
[children addObject:insetSpec];
// username / photo location header vertical stack
_userNameLabel.flexShrink = YES;
_photoLocationLabel.flexShrink = YES;
ASStackLayoutSpec *headerSubStack = [ASStackLayoutSpec verticalStackLayoutSpec];
headerSubStack.flexShrink = YES;
if (_photoLocationLabel.attributedString) {
[headerSubStack setChildren:@[_userNameLabel, _photoLocationLabel]];
} else {
[headerSubStack setChildren:@[_userNameLabel]];
}
[children addObject:_textNode1];
[children addObject:_textNode2];
[children addObject:_textNode3];
// header stack
_textNode1.flexShrink = YES;
_textNode2.flexShrink = YES;
_textNode3.flexShrink = YES;
ASStackLayoutSpec *innerStack = [ASStackLayoutSpec verticalStackLayoutSpec];
innerStack.children = children;
innerStack.flexGrow = YES;
innerStack.flexShrink = YES;
// _individualColorNode.preferredFrameSize = CGSizeMake(100, 600);
_individualColorNode.flexGrow = YES;
_individualColorNode.flexShrink = YES;
_userAvatarImageView.preferredFrameSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT);
_photoTimeIntervalSincePostLabel.spacingBefore = HORIZONTAL_BUFFER; // hack to remove double spaces around spacer
ASStackLayoutSpec *outerStack = [ASStackLayoutSpec horizontalStackLayoutSpec];
outerStack.flexGrow = YES;
outerStack.flexShrink = YES;
outerStack.children = @[innerStack, _individualColorNode];
outerStack.alignItems = ASStackLayoutAlignItemsStretch;
UIEdgeInsets avatarInsets = UIEdgeInsetsMake(HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER);
ASInsetLayoutSpec *avatarInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:avatarInsets child:_userAvatarImageView];
return outerStack;
ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
spacer.flexGrow = YES;
spacer.flexShrink = YES;
ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec];
headerStack.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horizontal stack
headerStack.justifyContent = ASStackLayoutJustifyContentStart; // justify content to the left side of the header stack
headerStack.flexShrink = YES;
headerStack.flexGrow = YES;
[headerStack setChildren:@[avatarInset, headerSubStack, spacer, _photoTimeIntervalSincePostLabel]];
// header inset stack
UIEdgeInsets insets = UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER);
ASInsetLayoutSpec *headerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:headerStack];
headerWithInset.flexShrink = YES;
headerWithInset.flexGrow = YES;
// footer stack
ASStackLayoutSpec *footerStack = [ASStackLayoutSpec verticalStackLayoutSpec];
footerStack.spacing = VERTICAL_BUFFER;
[footerStack setChildren:@[_photoLikesLabel, _photoDescriptionLabel]];
// footer inset stack
UIEdgeInsets footerInsets = UIEdgeInsetsMake(VERTICAL_BUFFER, HORIZONTAL_BUFFER, VERTICAL_BUFFER, HORIZONTAL_BUFFER);
ASInsetLayoutSpec *footerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:footerInsets child:footerStack];
// vertical stack
CGFloat cellWidth = constrainedSize.max.width;
_photoImageView.preferredFrameSize = CGSizeMake(cellWidth, cellWidth); // constrain photo frame size
ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec];
verticalStack.alignItems = ASStackLayoutAlignItemsStretch; // sretch headerStack to fill horizontal space
[verticalStack setChildren:@[headerWithInset, _photoImageView, footerWithInset]];
verticalStack.flexShrink = YES;
return verticalStack;
}
//- (ASSizeRange)playgroundConstrainedSize
//{
// if (ASRangeIsEmpty(_playgroundConstrainedSize)) {
// CGSize maxSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
// _playgroundConstrainedSize = ASSizeRangeMake(maxSize, maxSize);
// }
// return _playgroundConstrainedSize;
//}
//
//- (ASSizeRange)nodeConstrainedSize
//{
// return self.playgroundConstrainedSize;
//}
#pragma mark - helper methods
- (NSAttributedString *)usernameAttributedStringWithFontSize:(CGFloat)size
{
return [NSAttributedString attributedStringWithString:@"hannahmbanana"
fontSize:size
color:[UIColor darkBlueColor]
firstWordColor:nil];
}
- (NSAttributedString *)locationAttributedStringWithFontSize:(CGFloat)size
{
return [NSAttributedString attributedStringWithString:@"San Fransisco, CA"
fontSize:size
color:[UIColor lightBlueColor]
firstWordColor:nil];
}
- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size
{
return [NSAttributedString attributedStringWithString:@"30m"
fontSize:size
color:[UIColor lightGrayColor]
firstWordColor:nil];
}
- (NSAttributedString *)likesAttributedStringWithFontSize:(CGFloat)size
{
return [NSAttributedString attributedStringWithString:@"♥︎ 17 likes"
fontSize:size
color:[UIColor darkBlueColor]
firstWordColor:nil];
}
- (NSAttributedString *)descriptionAttributedStringWithFontSize:(CGFloat)size
{
NSString *string = [NSString stringWithFormat:@"hannahtroisi check out this cool pic from the internet!"];
NSAttributedString *attrString = [NSAttributedString attributedStringWithString:string
fontSize:size
color:[UIColor darkGrayColor]
firstWordColor:[UIColor darkBlueColor]];
return attrString;
}
@end

View File

@@ -0,0 +1,39 @@
//
// Utilities.h
// Flickrgram
//
// Created by Hannah Troisi on 3/9/16.
// Copyright © 2016 Hannah Troisi. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface UIColor (Additions)
+ (UIColor *)darkBlueColor;
+ (UIColor *)lightBlueColor;
@end
@interface UIImage (Additions)
+ (UIImage *)followingButtonStretchableImageForCornerRadius:(CGFloat)cornerRadius following:(BOOL)followingEnabled;
- (UIImage *)makeCircularImageWithSize:(CGSize)size;
@end
@interface NSString (Additions)
// returns a user friendly elapsed time such as '50s', '6m' or '3w'
+ (NSString *)elapsedTimeStringSinceDate:(NSString *)uploadDateString;
@end
@interface NSAttributedString (Additions)
+ (NSAttributedString *)attributedStringWithString:(NSString *)string fontSize:(CGFloat)size
color:(UIColor *)color firstWordColor:(UIColor *)firstWordColor;
@end

View File

@@ -0,0 +1,188 @@
//
// Utilities.m
// Flickrgram
//
// Created by Hannah Troisi on 3/9/16.
// Copyright © 2016 Hannah Troisi. All rights reserved.
//
#import "Utilities.h"
#import <UIKit/UIKit.h>
#define StrokeRoundedImages 0
@implementation UIColor (Additions)
+ (UIColor *)darkBlueColor
{
return [UIColor colorWithRed:18.0/255.0 green:86.0/255.0 blue:136.0/255.0 alpha:1.0];
}
+ (UIColor *)lightBlueColor
{
return [UIColor colorWithRed:0.0 green:122.0/255.0 blue:1.0 alpha:1.0];
}
@end
@implementation UIImage (Additions)
+ (UIImage *)followingButtonStretchableImageForCornerRadius:(CGFloat)cornerRadius following:(BOOL)followingEnabled
{
CGSize unstretchedSize = CGSizeMake(2 * cornerRadius + 1, 2 * cornerRadius + 1);
CGRect rect = (CGRect) {CGPointZero, unstretchedSize};
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:cornerRadius];
// create a graphics context for the following status button
UIGraphicsBeginImageContextWithOptions(unstretchedSize, NO, 0);
[path addClip];
if (followingEnabled) {
[[UIColor whiteColor] setFill];
[path fill];
path.lineWidth = 3;
[[UIColor lightBlueColor] setStroke];
[path stroke];
} else {
[[UIColor lightBlueColor] setFill];
[path fill];
}
UIImage *followingBtnImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage *followingBtnImageStretchable = [followingBtnImage stretchableImageWithLeftCapWidth:cornerRadius
topCapHeight:cornerRadius];
return followingBtnImageStretchable;
}
- (UIImage *)makeCircularImageWithSize:(CGSize)size
{
// make a CGRect with the image's size
CGRect circleRect = (CGRect) {CGPointZero, size};
// begin the image context since we're not in a drawRect:
UIGraphicsBeginImageContextWithOptions(circleRect.size, NO, 0);
// create a UIBezierPath circle
UIBezierPath *circle = [UIBezierPath bezierPathWithRoundedRect:circleRect cornerRadius:circleRect.size.width/2];
// clip to the circle
[circle addClip];
// draw the image in the circleRect *AFTER* the context is clipped
[self drawInRect:circleRect];
// create a border (for white background pictures)
#if StrokeRoundedImages
circle.lineWidth = 1;
[[UIColor darkGrayColor] set];
[circle stroke];
#endif
// get an image from the image context
UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext();
// end the image context since we're not in a drawRect:
UIGraphicsEndImageContext();
return roundedImage;
}
@end
@implementation NSString (Additions)
// Returns a user-visible date time string that corresponds to the
// specified RFC 3339 date time string. Note that this does not handle
// all possible RFC 3339 date time strings, just one of the most common
// styles.
+ (NSDate *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString
{
NSDateFormatter * rfc3339DateFormatter;
NSLocale * enUSPOSIXLocale;
// Convert the RFC 3339 date time string to an NSDate.
rfc3339DateFormatter = [[NSDateFormatter alloc] init];
enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
[rfc3339DateFormatter setLocale:enUSPOSIXLocale];
[rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ'"];
[rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
return [rfc3339DateFormatter dateFromString:rfc3339DateTimeString];
}
+ (NSString *)elapsedTimeStringSinceDate:(NSString *)uploadDateString
{
// early return if no post date string
if (!uploadDateString)
{
return @"NO POST DATE";
}
NSDate *postDate = [self userVisibleDateTimeStringForRFC3339DateTimeString:uploadDateString];
if (!postDate) {
return @"DATE CONVERSION ERROR";
}
NSDate *currentDate = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSUInteger seconds = [[calendar components:NSCalendarUnitSecond fromDate:postDate toDate:currentDate options:0] second];
NSUInteger minutes = [[calendar components:NSCalendarUnitMinute fromDate:postDate toDate:currentDate options:0] minute];
NSUInteger hours = [[calendar components:NSCalendarUnitHour fromDate:postDate toDate:currentDate options:0] hour];
NSUInteger days = [[calendar components:NSCalendarUnitDay fromDate:postDate toDate:currentDate options:0] day];
NSString *elapsedTime;
if (days > 7) {
elapsedTime = [NSString stringWithFormat:@"%luw", (long)ceil(days/7.0)];
} else if (days > 0) {
elapsedTime = [NSString stringWithFormat:@"%lud", (long)days];
} else if (hours > 0) {
elapsedTime = [NSString stringWithFormat:@"%luh", (long)hours];
} else if (minutes > 0) {
elapsedTime = [NSString stringWithFormat:@"%lum", (long)minutes];
} else if (seconds > 0) {
elapsedTime = [NSString stringWithFormat:@"%lus", (long)seconds];
} else if (seconds == 0) {
elapsedTime = @"1s";
} else {
elapsedTime = @"ERROR";
}
return elapsedTime;
}
@end
@implementation NSAttributedString (Additions)
+ (NSAttributedString *)attributedStringWithString:(NSString *)string fontSize:(CGFloat)size
color:(nullable UIColor *)color firstWordColor:(nullable UIColor *)firstWordColor
{
NSDictionary *attributes = @{NSForegroundColorAttributeName: color ? : [UIColor blackColor],
NSFontAttributeName: [UIFont systemFontOfSize:size]};
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string];
[attributedString addAttributes:attributes range:NSMakeRange(0, string.length)];
if (firstWordColor) {
NSRange firstSpaceRange = [string rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]];
NSRange firstWordRange = NSMakeRange(0, firstSpaceRange.location);
[attributedString addAttribute:NSForegroundColorAttributeName value:firstWordColor range:firstWordRange];
}
return attributedString;
}
@end

View File

@@ -62,4 +62,11 @@
[self.view setNeedsLayout];
}
- (void)toggleVizualization:(BOOL)toggle
{
NSLog(@"shouldVisualizeLayoutSpecs:%d", toggle);
[self.node shouldVisualizeLayoutSpecs:toggle];
[self.view setNeedsLayout];
}
@end

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B