2016-03-01 83 views
3

如何使用分隔符調整視圖大小?我試圖做的是像Instagram layout應用程序。我希望能夠通過拖動分隔視圖的線來調整視圖大小。iOS - 使用觸摸拖動分隔符調整多個視圖的大小

我已經看過這個question。它與我想要完成的類似,並且我已經嘗試了答案,但如果有超過2個視圖連接到分隔符(如果有3個或更多視圖只有2個視圖會在每次分隔符移動時調整大小),則不起作用。我試圖改變代碼,但我不知道該做什麼或代碼的含義。

在我的應用程序中,我將有2-6的意見。分隔符應調整其旁邊的所有視圖。

我的看法的一些例子:

enter image description here

我怎樣才能做到這一點?我從哪說起呢?

回答

6

有很多方法可以做到這一點,但像Avinash,我建議在各種「內容」UIView對象之間創建一個「分隔視圖」。然後你可以拖動它。然而,這裏的訣竅在於,您可能希望分隔視圖的尺寸大於窄視線的尺寸,這樣它不僅可以捕捉分隔線上的觸摸,而且還可以接近分隔線。與其他答案不同,現在我推薦使用自動佈局,以便用戶使用手勢所需的所有內容都是更新分隔視圖的位置(例如,更新分隔視圖的頂部約束),然後所有其他視圖將自動爲您調整大小。我還建議在子視圖的大小上添加一個低優先級約束,以便在首次設置所有內容時開始拖動分隔符,但在拖動分隔符指示時會優雅地失敗相鄰視圖的大小必須改變。最後,雖然我們歷史上會使用手勢識別器來處理這樣的事情,但隨着iOS 9中預測觸摸的出現,我建議只實施touchesBegan,touchesMoved等。使用預測觸摸,您將不會注意到在模擬器或較舊設備上的差異,但是當您在具有預測觸摸功能的設備上運行此功能時(例如iPad Pro和其他新設備等新設備),您將獲得更具響應性的用戶體驗。

所以水平分隔視圖類可能如下所示。

static CGFloat const kTotalHeight = 44;        // the total height of the separator (including parts that are not visible 
static CGFloat const kVisibleHeight = 2;        // the height of the visible portion of the separator 
static CGFloat const kMargin = (kTotalHeight - kVisibleHeight)/2.0; // the height of the non-visible portions of the separator (i.e. above and below the visible portion) 
static CGFloat const kMinHeight = 10;         // the minimum height allowed for views above and below the separator 

/** Horizontal separator view 

@note This renders a separator view, but the view is larger than the visible separator 
line that you see on the device so that it can receive touches when the user starts 
touching very near the visible separator. You always want to allow some margin when 
trying to touch something very narrow, such as a separator line. 
*/ 

@interface HorizontalSeparatorView : UIView 

@property (nonatomic, strong) NSLayoutConstraint *topConstraint;  // the constraint that dictates the vertical position of the separator 
@property (nonatomic, weak) UIView *firstView;      // the view above the separator 
@property (nonatomic, weak) UIView *secondView;      // the view below the separator 

// some properties used for handling the touches 

@property (nonatomic) CGFloat oldY;         // the position of the separator before the gesture started 
@property (nonatomic) CGPoint firstTouch;        // the position where the drag gesture started 

@end 

@implementation HorizontalSeparatorView 

#pragma mark - Configuration 

/** Add a separator between views 

This creates the separator view; adds it to the view hierarchy; adds the constraint for height; 
adds the constraints for leading/trailing with respect to its superview; and adds the constraints 
the relation to the views above and below 

@param firstView The UIView above the separator 
@param secondView The UIView below the separator 
@returns   The separator UIView 
*/ 

+ (instancetype)addSeparatorBetweenView:(UIView *)firstView secondView:(UIView *)secondView { 
    HorizontalSeparatorView *separator = [[self alloc] init]; 
    [firstView.superview addSubview:separator]; 
    separator.firstView = firstView; 
    separator.secondView = secondView; 

    [NSLayoutConstraint activateConstraints:@[ 
     [separator.heightAnchor constraintEqualToConstant:kTotalHeight], 
     [separator.superview.leadingAnchor constraintEqualToAnchor:separator.leadingAnchor], 
     [separator.superview.trailingAnchor constraintEqualToAnchor:separator.trailingAnchor], 
     [firstView.bottomAnchor constraintEqualToAnchor:separator.topAnchor constant:kMargin], 
     [secondView.topAnchor constraintEqualToAnchor:separator.bottomAnchor constant:-kMargin], 
    ]]; 

    separator.topConstraint = [separator.topAnchor constraintEqualToAnchor:separator.superview.topAnchor constant:0]; // it doesn't matter what the constant is, because it hasn't been enabled 

    return separator; 
} 

- (instancetype)init { 
    self = [super init]; 
    if (self) { 
     self.translatesAutoresizingMaskIntoConstraints = false; 
     self.userInteractionEnabled = true; 
     self.backgroundColor = [UIColor clearColor]; 
    } 
    return self; 
} 

#pragma mark - Handle Touches 

// When it first receives touches, save (a) where the view currently is; and (b) where the touch started 

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 
    self.oldY = self.frame.origin.y; 
    self.firstTouch = [[touches anyObject] locationInView:self.superview]; 
    self.topConstraint.constant = self.oldY; 
    self.topConstraint.active = true; 
} 

// When user drags finger, figure out what the new top constraint should be 

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 
    UITouch *touch = [touches anyObject]; 

    // for more responsive UX, use predicted touches, if possible 

    if ([UIEvent instancesRespondToSelector:@selector(predictedTouchesForTouch:)]) { 
     UITouch *predictedTouch = [[event predictedTouchesForTouch:touch] lastObject]; 
     if (predictedTouch) { 
      [self updateTopConstraintOnBasisOfTouch:predictedTouch]; 
      return; 
     } 
    } 

    // if no predicted touch found, just use the touch provided 

    [self updateTopConstraintOnBasisOfTouch:touch]; 
} 

// When touches are done, reset constraint on the basis of the final touch, 
// (backing out any adjustment previously done with predicted touches, if any). 

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 
    [self updateTopConstraintOnBasisOfTouch:[touches anyObject]]; 
} 

/** Update top constraint of the separator view on the basis of a touch. 

This updates the top constraint of the horizontal separator (which moves the visible separator). 
Please note that this uses properties populated in touchesBegan, notably the `oldY` (where the 
separator was before the touches began) and `firstTouch` (where these touches began). 

@param touch The touch that dictates to where the separator should be moved. 
*/ 
- (void)updateTopConstraintOnBasisOfTouch:(UITouch *)touch { 
    // calculate where separator should be moved to 

    CGFloat y = self.oldY + [touch locationInView:self.superview].y - self.firstTouch.y; 

    // make sure the views above and below are not too small 

    y = MAX(y, self.firstView.frame.origin.y + kMinHeight - kMargin); 
    y = MIN(y, self.secondView.frame.origin.y + self.secondView.frame.size.height - (kMargin + kMinHeight)); 

    // set constraint 

    self.topConstraint.constant = y; 
} 

#pragma mark - Drawing 

- (void)drawRect:(CGRect)rect { 
    CGRect separatorRect = CGRectMake(0, kMargin, self.bounds.size.width, kVisibleHeight); 
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:separatorRect]; 
    [[UIColor blackColor] set]; 
    [path stroke]; 
    [path fill]; 
} 

@end 

一個垂直分隔符可能看起來很相似,但我會爲你保留這個練習。

無論如何,你可以使用它像這樣:

@implementation ViewController 

- (void)viewDidLoad { 
    [super viewDidLoad]; 

    UIView *previousContentView = nil; 

    for (NSInteger i = 0; i < 4; i++) { 
     UIView *contentView = [self addRandomColoredView]; 
     [self.view.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor].active = true; 
     [self.view.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor].active = true; 
     if (previousContentView) { 
      [HorizontalSeparatorView addSeparatorBetweenView:previousContentView secondView:contentView]; 
      NSLayoutConstraint *height = [contentView.heightAnchor constraintEqualToAnchor:previousContentView.heightAnchor]; 
      height.priority = 250; 
      height.active = true; 
     } else { 
      [self.view.topAnchor constraintEqualToAnchor:contentView.topAnchor].active = true; 
     } 
     previousContentView = contentView; 
    } 
    [self.view.bottomAnchor constraintEqualToAnchor:previousContentView.bottomAnchor].active = true; 
} 

- (UIView *)addRandomColoredView { 
    UIView *someView = [[UIView alloc] init]; 
    someView.translatesAutoresizingMaskIntoConstraints = false; 
    someView.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0]; 
    [self.view addSubview:someView]; 

    return someView; 
} 

@end 

能產生類似:

enter image description here

正如我所說,垂直分割看起來非常相似。如果使用垂直和水平分隔符都有複雜的視圖,則可能希望使用不可見的容器視圖來隔離垂直和水平視圖。例如,請考慮您的例子之一:

enter image description here

這可能會包括跨越設備與單個水平分離器的整個寬度,然後頂視圖會,本身有兩個兩個視圖具有一個垂直分隔符的子視圖和底部視圖將具有三個具有兩個垂直分隔符的子視圖。


有很多在這裏,所以你嘗試推斷上面的例子來處理(一)垂直分割線之前;然後(b)視圖內視圖模式,確保您真正理解上述示例的工作原理。這不是一個普遍的解決方案,而只是爲了說明你可能採用的模式。但希望這可以說明基本的想法。

+0

謝謝你非常搶!我會盡可能多地理解這一點! – imstillalive

+0

Hi Rob。我試圖創建垂直分隔符。 '[firstView.rightAnchor constraintEqualToAnchor:separator.leftAnchor constant:kMargin], [secondView.leftAnchor constraintEqualToAnchor:separator.rightAnchor constant:kMargin],'但是我的ViewController沒有顯示任何內容。我可以在哪裏向您展示我的整個代碼? – imstillalive

+0

Hi Rob。我在這裏用我的垂直分隔符代碼問了一個新問題:http://stackoverflow.com/questions/35775231/ios-resize-vertical-views-with-separator-view你能指導我做什麼我做錯了嗎?我嘗試將contentView右側錨點連接到分隔符左側錨點,將contentView左側錨點連接到分隔符右側錨點,但是當我運行代碼時。它顯示我一個空白的屏幕。 – imstillalive

0

使用UIPanGestureRecognizers。爲每個視圖添加一個識別器。在gestureRecognizerShouldBegin:方法中,如果手勢的位置非常接近邊緣,則返回YES(使用手勢的locationInView:view方法)。然後,在手勢的操作方法(手勢的initWithTarget: action:指定)您proccess你的動作是這樣的:

-(void)viewPan:(UIPanGestureRecognizer *)sender 

    switch (sender.state) { 

    case UIGestureRecognizerStateBegan: { 

     //determine the second view based on gesture's locationInView: 
     //for instance if close to bottom, the second view is the one under the current. 

    } 

    case UIGestureRecognizerStateChanged: { 
     //change the frames of the current and the second view based on sender's translationInView: 
    } 

    ... 
} 
0

按我所知,我們可以使用UIGestureRecognizer和經銷商的佈局做到這一點

1.使用UIView作爲行分隔符。
2.將Pan gestureRecognizer添加到分隔線視圖。使用UIView.animatewithDuration()
PanGestureRecognizer

最重要的是,不要忘記設置/查看UserInteration啓用在屬性檢查器的所有行分隔鑑於委託協議方法
3.拉手視圖運動。

2

基礎上Rob`s解決方案,我創建了斯威夫特類水平和垂直分離的觀點: https://gist.github.com/JULI-ya/1a7c293b022207bb427caa3bbb9d3ed8

只有與分離器兩個內部意見的代碼,因爲我的想法是,讓每個到其他創建這個自定義佈局。它看起來像一個二叉樹視圖結構。

+0

我該如何在視圖控制器中設置它?例如,這個佈局:http://i.stack.imgur.com/EA6uY.png – cannyboy

+0

@cannyboy在圖像上有3個分隔符視圖。每個其他內部。其餘邏輯是基於簡單視圖的佈局構建。 – JULIIncognito

1

我已將@ JULIIncognito的Swift類更新到Swift 4,添加了拖動指示器並修復了一些錯別字。

SeparatorView

方法導入到你的項目,並使用它像這樣:

SeparatorView.addSeparatorBetweenViews(separatorType: .horizontal, primaryView: view1, secondaryView: view2, parentView: self.view) 

這是怎麼看起來像(在上面的MapView,TableView中的底部): SeparatorView