I'm really stuck with this issue. I'm trying to add a button on top of an AVPlayerViewController that says "Skip Intro" (similar to the button that Netflix has on their content).
I've added 4 or 5 buttons horizontally starting just above the left side of the seek bar and each button are spaced about 10 points apart. I only need 1 button, but at this point I cannot figure out why I can't click any of these buttons at all:
I've declared the button and the focus guide variables as private:
Here's the viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
self.avPlayerViewController = [[AVPlayerViewController alloc] init];
[self addChildViewController:self.avPlayerViewController];
[self.view addSubview:self.avPlayerViewController.view];
self.avPlayerViewController.showsPlaybackControls = YES;
self.avPlayerViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:self.avPlayerViewController.view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0];
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:self.avPlayerViewController.view
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0.0];
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:self.avPlayerViewController.view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:self.avPlayerViewController.view
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0.0];
[self.view addConstraint:height];
[self.view addConstraint:width];
[self.view addConstraint:top];
[self.view addConstraint:leading];
[self.avPlayerViewController didMoveToParentViewController:self];
[self setupBreakButtonWithWidth:225 height:50 xCoordinate:100 yCoordinate:850];
[self setupBreakButtonWithWidth:225 height:50 xCoordinate:335 yCoordinate:850];
[self setupBreakButtonWithWidth:225 height:50 xCoordinate:570 yCoordinate:850];
[self setupBreakButtonWithWidth:225 height:50 xCoordinate:805 yCoordinate:850];
[self setupFocusGuide];
[self createBannerAdView];
[self resetUpNextState];
}
As you can see, I'm just adding buttons to the bottom of the screen (above the seek bar) and then I setup the focus guide and the preferredFocusEnvironments:
- (NSArray<id<UIFocusEnvironment>> *)preferredFocusEnvironments {
NSLog(#"**** BMNativeVideoPlayerViewController -> preferredFocusEnvironments");
return #[self.skipBreakButton];
}
- (void)setupFocusGuide
{
NSLog(#"**** BMNativeVideoPlayerViewController -> setupFocusGuide");
// allocate focus guide and add it to the view
self.focusGuide = [[UIFocusGuide alloc] init];
[self.view addLayoutGuide:self.focusGuide];
// define constraints
[self.focusGuide.leftAnchor constraintEqualToAnchor:self.view.leftAnchor].active = YES;
[self.focusGuide.rightAnchor constraintEqualToAnchor:self.view.rightAnchor].active = YES;
[self.focusGuide.topAnchor constraintEqualToAnchor:_skipBreakButton.topAnchor].active = YES;
[self.focusGuide.bottomAnchor constraintEqualToAnchor:_skipBreakButton.bottomAnchor].active = YES;
// select the default focusable view
self.focusGuide.preferredFocusEnvironments = #[self.skipBreakButton];
}
Here is the method where the button is configured, along with the button handler method and the didUpdateFocusInContext method (although I don't know if it's doing what it's supposed to):
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
self.focusGuide.preferredFocusEnvironments = #[self.skipBreakButton];
}
- (void)setupBreakButtonWithWidth:(CGFloat)width height:(CGFloat)height xCoordinate:(CGFloat)x yCoordinate:(CGFloat)y
{
NSLog(#"**** BMNativeVideoPlayerViewController -> setupBreakButton");
_skipBreakButton = [[UIButton alloc] init];
_skipBreakButton.backgroundColor = [UIColor whiteColor];
[self.skipBreakButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
_skipBreakButton.alpha = 1.0f;
_skipBreakButton.layer.cornerRadius = 3;
_skipBreakButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.avPlayerViewController.contentOverlayView addSubview:_skipBreakButton];
[self.avPlayerViewController.contentOverlayView bringSubviewToFront:_skipBreakButton];
NSLayoutConstraint *xConstraint = [NSLayoutConstraint
constraintWithItem:_skipBreakButton attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual toItem:self.view attribute:
NSLayoutAttributeLeading multiplier:1.0 constant:x];
NSLayoutConstraint *yConstraint = [NSLayoutConstraint
constraintWithItem:_skipBreakButton attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual toItem:self.view attribute:
NSLayoutAttributeTop multiplier:1.0f constant:y];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:_skipBreakButton
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:width];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:_skipBreakButton
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:height];
[self.view addConstraints:#[xConstraint, yConstraint, widthConstraint, heightConstraint]];
[self.skipBreakButton setUserInteractionEnabled:YES];
[self.skipBreakButton setTitle:#"Skip Intro" forState:UIControlStateNormal];
[self.skipBreakButton.titleLabel setFont:[UIFont fontWithName:#"Helvetica-Bold" size:26.0]];
NSLog(#"**** Can the skip break button be focused? %d", self.skipBreakButton.canBecomeFocused);
[self.skipBreakButton addTarget:self action:#selector(skipBreakButtonClicked:) forControlEvents:UIControlEventPrimaryActionTriggered];
}
- (void)skipBreakButtonClicked:(UIButton *) sender {
NSLog(#"**** BMNativeVideoPlayerViewController -> skipBreakButtonClicked");
}
I cannot click on any of these buttons. I need help to figure out how to get it to work.
It seems that a view is on top of this button but I'm not sure how to fix it:
(lldb) po [UIFocusDebugger checkFocusabilityForItem: 0x7ff86fe60510]
The following issues were found that would prevent this item from being focusable:
- ISSUE: One or more ancestors have issues that may be preventing this item from being focusable. Details:
<_AVPlayerViewControllerContainerView 0x7ff86fc29eb0>:
- ISSUE: This view returns YES from -canBecomeFocused, which will prevent its subviews from being focusable.
Please advise?
AVPlayer view controller steals all of the view inputs when it is full screen. In my experience what you have to do is keep the player 1 pixel smaller than the full screen when you have your custom elements and then when your custom elements aren't on the screen you can make it full size. If you set breakpoints in your focus override code you will see that it doesn't hit and this is because Apple is controlling the inputs on their AVPlayerViewController.
If you try the above it should resolve the issue.
You could achieve this behaviour by presenting a modal view controller (with a skip button on it) as overlay over the context of the player.
You can then dismiss the modal view controller after a few seconds (as done in the example below), or programatically if you know when the intro actually finishes.
import UIKit
import AVFoundation
import AVKit
class ThePlayerViewController: AVPlayerViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
play(stream: URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8")!)
presentSkipOverlay()
}
// MARK: - Private
private func play(stream: URL) {
let asset = AVAsset(url: stream)
let playetItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: playetItem)
player?.play()
}
private func presentSkipOverlay() {
let skipOverlayViewController = SkipOverlayViewController()
skipOverlayViewController.onSkip = {
[weak self] in
// Skip the intro here
self?.player?.seek(to: CMTime(seconds: 60.0, preferredTimescale: 1))
}
skipOverlayViewController.modalPresentationStyle = .overCurrentContext
skipOverlayViewController.accessibilityViewIsModal = true
present(skipOverlayViewController, animated: true, completion: {
// Dismiss the overlay automatically after 3 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
skipOverlayViewController.dismiss(animated: true, completion: nil)
}
})
}
}
And this would be a naive implementation of the Overlay view controller:
final class SkipOverlayViewController: UIViewController {
var onSkip: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
setUpView()
}
// MARK: - Private
private lazy var skipButton: UIButton = {
let skipButton = UIButton()
skipButton.backgroundColor = .white
skipButton.setTitleColor(.black, for: .normal)
skipButton.setTitle("Skip Intro", for: .normal)
skipButton.addTarget(self, action: #selector(skipButtonWasPressed), for: .primaryActionTriggered)
return skipButton
}()
private func setUpView() {
view.addSubview(skipButton)
skipButton.translatesAutoresizingMaskIntoConstraints = false
skipButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
skipButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -200).isActive = true
skipButton.widthAnchor.constraint(equalToConstant: 225).isActive = true
}
// MARK: - Actions
#objc
func skipButtonWasPressed() {
print("Skip button was Pressed")
onSkip?()
}
}
And this is the result:
Related
I am on macOS 10.16 (BigSur) XCode 12, objective-c.
I have create a NSTableView within an NSScroll View programmatically and filled it with dummy data.
I CAN scroll down while i CANNOT scroll left and right. So i assume there's some misconfiguration in the code but i can't figure it out.
Here's the behaviour -as you can see theres more content on the right, but scrollbar doesn't scroll:
And here's the code:
NSEdgeInsets insets = NSEdgeInsetsMake (0,0,0,0); // This is usually dynamic, just for demo purposes
NSEdgeInsets margins = NSEdgeInsetsMake (0,0,0,0); // This is usually dynamic, just for demo purposes
// Create tableView
NSTableView* tableView = [[NSTableView alloc] initWithFrame:NSZeroRect];
tableView.translatesAutoresizingMaskIntoConstraints = NO;
tableView.backgroundColor = [NSColor clearColor];
tableView.delegate = self.delegate ? self.delegate : weakSelf;
tableView.dataSource = self.delegate ? self.delegate : weakSelf;
tableView.rowHeight = [SHDataTableCellView height];
tableView.intercellSpacing = NSMakeSize(2, 2);
tableView.headerView = [[NSTableHeaderView alloc] initWithFrame:NSMakeRect(0, 0, 0, CGFLOAT_MIN)];
if (#available(macOS 11.0, *)) {tableView.style = NSTableViewStylePlain;}
self.tableView = tableView;
// Create Scroll View
NSScrollView* scrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect];
// Setup scrollView
scrollView.drawsBackground = NO;
scrollView.hasVerticalScroller = YES;
scrollView.hasHorizontalScroller = YES;
scrollView.autohidesScrollers = NO;
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.documentView = tableView;
scrollView.scrollerStyle = NSScrollerKnobStyleDefault;
scrollView.automaticallyAdjustsContentInsets = NO;
scrollView.contentInsets = insets;
scrollView.scrollerInsets = NSEdgeInsetsMake(-(insets.top), -(insets.left), -(insets.bottom), -(insets.right));
self.scrollView = scrollView;
[self.view addSubview:scrollView];
// Add constraints
NSDictionary *viewBindings = NSDictionaryOfVariableBindings(view,scrollView);
NSDictionary *metrics = #{ #"marginLeft" : #(margins.left),
#"marginRight" : #(margins.right),
#"marginBottom" : #(margins.bottom),
#"marginTop" : #(margins.top),
#"insetLeft" : #(insets.left),
#"insetRight" : #(insets.right),
#"insetBottom" : #(insets.bottom),
#"insetTop" : #(insets.top)};
[view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(marginLeft)-[scrollView]-(marginRight)-|" options:0 metrics:metrics views:viewBindings]];
[view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(marginTop)-[scrollView]-(marginBottom)-|" options:0 metrics:metrics views:viewBindings]];
[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.tableView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:scrollView
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0]];
[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:scrollView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:60]]; // equals 2 rows of content
NSEdgeInsets margin and insets are set to 0 in this example are tend to be used as dynamic variables (you may ignore them)
Removed the width constraint and it worked. Sometimes its just too obvious and you still don't see it.
I have a UIScrollView with pagingEnabled. Added 5 UIStackView to subview of UIScrollView and also had one UISegmentedControl. I want to show currently selected segment and also want to disable horizontal scrolling but the view will able to scroll vertically.
let take an example:
Let selected page index = 0
User not able to scroll or drag horizontally to navigate to next page.
The user can scroll vertically
Now User selected page index = 1 in UISegmentedControl
2nd page will be visible
Now user not able to scroll or drag to 0 and 2 index
How can I achieve this functionality using UIScrollView
Tried logic:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGFloat xOff = self.scrollView.frame.size.width * self.currentPage;
[self.scrollView setContentSize:CGSizeMake(xOff, self.scrollView.contentSize.height)];
//[self.scrollView setContentOffset:CGPointMake(xOff, 0) animated:true];
}
but using this UIScrollView after changing page When I scroll again go to 0 page.
You can use UICollectionView with paging enabled and inside you can implement UIScrollView with vertical scroll enable. Disable the UICollectionView scroll and change the index collection cell on click using below code:
accepted
New, Edited Answer:
Add this in viewDidLayoutSubviews
SWIFT
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let indexPath = IndexPath(item: 12, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: [ .centeredHorizontally], animated: true)
}
Normally, .centerVertically is the case
Objective-C
-(void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:12 inSection:0];
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}
I am using this solution from my own question
In segmentDidChange() method
[self.scrollView.subviews makeObjectsPerformSelector: #selector(removeFromSuperview)];
self.scrollView.showsVerticalScrollIndicator = false;
self.scrollView.showsHorizontalScrollIndicator = false;
self.arrSubViews = [NSMutableArray array];
// Loading view's from xib's
// Now
UIView *containerView = [[UIView alloc]initWithFrame:self.scrollView.frame];
containerView.translatesAutoresizingMaskIntoConstraints = false;
[self prepareStackViewforPage:index withContainerView:containerView];
Now preparing stackView for selected index
- (void)prepareStackViewforPage:(NSInteger)index withContainerView:(UIView *)containerView {
[self.scrollView addSubview:containerView];
[self applyConstraintsToParent:self.scrollView andSubView:containerView];
UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:self.arrSubViews];
[stackView setFrame:self.scrollView.frame];
stackView.translatesAutoresizingMaskIntoConstraints = false;
stackView.axis = UILayoutConstraintAxisVertical;
stackView.spacing = 0;
stackView.distribution = UIStackViewDistributionFill;
stackView.alignment = UIStackViewAlignmentFill;
[containerView addSubview:stackView];
[self applyConstraintsToParent:containerView andSubView:stackView];
[self.view updateConstraintsIfNeeded];
[self.view layoutIfNeeded];
[self.view layoutSubviews];
}
In applyConstraintsToParent:andSubView: method
- (void)applyConstraintsToParent:(UIView *)parentView andSubView:(UIView *)subView {
//constraints
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:parentView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0];
[parentView addConstraint:leading];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:parentView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0];
[parentView addConstraint:trailing];
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:parentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
[parentView addConstraint:top];
NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:parentView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-8];
[parentView addConstraint:bottom];
NSLayoutConstraint *equalWidth = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:parentView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0];
[parentView addConstraint:equalWidth];
leading.active = true;
trailing.active = true;
top.active = true;
bottom.active = true;
equalWidth.active = true;
}
Now it's just working fine but now I have following issues
View's added via xib height not changing dynamically
I have raised a question for this issue on UIStackView: Loading views from xib and updating height constraint of subView did not reflect any changes?
But this logic working fine in storyboard?
Following your question the answer in to set isScrollEnabled property of UIScrollView to false like following:
For Objective-C
[self.scrollView setScrollEnabled:NO];
For swift
self.scrollView.isScrollEnabled = false
As Apple documentation says about this property:
A Boolean value that determines whether scrolling is enabled.
If the value of this property is true , scrolling is enabled, and if it is false , scrolling is disabled. The default is true.
When scrolling is disabled, the scroll view does not accept touch events; it forwards them up the responder chain.
So your scrollView won't accept touches, but content of your scrollView will.
And this doesn't disallow you to scroll your scrollView programmatically (which is changing contentOffset property of your scrollView). So when selected segment of your UISegmentedControl has changed you should calculate new content offset for your scrollView depending on pages size and set it, I advise you to use setContentOffset(_:animated:) method of UIScrollView to achieve scrolling.
Hope it helps.
I have to alter constraint but by changing constraint constants,
I get duplicates - Xcode gives a warning.
I can see that one constraint is the old one and the other is the new one,
and their memory addresses are different.
What is the correct way to create and setup views to handle constraint changes?
I my example I have a mainView which added to self.view, the provided view
when you extend UIViewController. "self.view" is provided and has no constraints set.
Main MainView is my container and I want to adjust its height to be above the keyboard whenever the keyboard appears.
viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
_mainView = [UIView new];
_mainView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:_mainView];
}
viewWillLayoutSubviews:
-(void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
//moved to viewDidLoad
//self.mainView.translatesAutoresizingMaskIntoConstraints = NO;
//top
[[self view] addConstraint:[NSLayoutConstraint constraintWithItem:_mainView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0]];
//bottom
_mainBottomContraint = [NSLayoutConstraint constraintWithItem:_mainView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:_keyboardOffset];
[[self view] addConstraint:_mainBottomContraint];
//left
[[self view] addConstraint:[NSLayoutConstraint constraintWithItem:_mainView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0]];
//right
[[self view] addConstraint:[NSLayoutConstraint constraintWithItem:_mainView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0]];
-
Whenever I alter a constraint constant I get duplicate constraints,
viewWillLayoutSubviews gets called again and recreates the constraints.
How does one change the constraints without re-creation? Or what is the correct pattern to use here.
keyboardWillShow - changes the constraint (setConstant)
- (void)keyboardWillShow:(NSNotification*)aNotification {
if (![_chatTextField isFirstResponder]) {
return;
}
CGSize tabBarSize = [[[self tabBarController] tabBar] bounds].size;
NSDictionary* info = [aNotification userInfo];
NSTimeInterval duration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve curve = [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue];
CGSize kbSize = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGRect bkgndRect = _mainView.frame;
bkgndRect.size.height -= kbSize.height-tabBarSize.height;
//animate with keyboard
_keyboardOffset = -kbSize.height-tabBarSize.height;
[_mainBottomContraint setConstant:_keyboardOffset];
[self.view setNeedsLayout];
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState | curve animations:^{
[_mainView layoutIfNeeded];
} completion:nil];
}
LOGS
Constraint 1 at memory address 0x174286450
Constraint 2 at memory address 0x174288660
(
"<NSLayoutConstraint:0x174286450 UIView:0x14fd5e5b0.bottom == UIView:0x14fe5edd0.bottom - 271 (active)>",
"<NSLayoutConstraint:0x174288660 UIView:0x14fd5e5b0.bottom == UIView:0x14fe5edd0.bottom (active)>"
)
In my opinions,you add the _mainBottomContraint twice.First,you creat a _mainBottomContraint and retain it,when the keyboard change,the _mainBottomContraint.constaints changes,and if the view in self.view.subviews change the frame,the system will post a notification,and the viewcontroller will recieve it,and call viewWillLayoutSubviews,like the view will call layoutSubviews.So you creat a _mainBottomContraint,so now,there are two bottomContraint and you get a warning.
You should add the contraints only once,you can add them in viewDidLoad function,and it will be good.
Call
subview.translatesAutoresizingMaskIntoConstraints = NO;
on the subView BEFORE adding it to another view. Otherwise some constraints get created automatically leading to warnings.
- (void)viewDidLoad {
[super viewDidLoad]; _mainView = [UIView new];
self.mainView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:_mainView];
}
Then save references of your created constraints (like you do) and change them whenever neccesary.
When my table view is empty, I want to add a simple label into the tableview footer to display a "no content" message. It was working fine when I was setting the frames of the UILabel and the UIView that I used for footerView, but now I'm trying to convert to using auto-layout, and I can't get it to work.
Here's what I'm doing:
// Create the footer
UIView *tempFooter = [[UIView alloc] init];
UILabel *atext = [[UILabel alloc] init];
tempFooter.translatesAutoresizingMaskIntoConstraints = NO;
atext.translatesAutoresizingMaskIntoConstraints = NO;
atext.numberOfLines = 0;
[atext setText:#"Add Some Text"];
atext.textAlignment = NSTextAlignmentCenter;
atext.font = [UIFont italicSystemFontOfSize:14];
atext.textColor = [UIColor grayColor];
atext.backgroundColor = [UIColor clearColor];
atext.lineBreakMode = NSLineBreakByWordWrapping;
// add label to footer view, and add constraints
[tempFooter addSubview:atext];
NSLayoutConstraint *tvatt1 = [NSLayoutConstraint constraintWithItem:tempFooter
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:atext attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:10.0];
NSLayoutConstraint *tvatt2 = [NSLayoutConstraint constraintWithItem:tempFooter
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:atext
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:10.0];
NSLayoutConstraint *tvatt3 = [NSLayoutConstraint constraintWithItem:tempFooter
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:atext
attribute:NSLayoutAttributeTop
multiplier:5.0
constant:0];
NSLayoutConstraint *tvatt4 = [NSLayoutConstraint constraintWithItem:tempFooter
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:atext
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant: 0];
[tempFooter addConstraints: #[tvatt1, tvatt2, tvatt3, tvatt4]];
// set the footerview
self.tview.tableFooterView = tempFooter;
When I run this, I get a crash:
2014-09-08 19:57:16.594 SimpleList[49442:60b] * Assertion failure in
-[UITableView layoutSublayersOfLayer:], /SourceCache/UIKit_Sim/UIKit-2935.137/UIView.m:8794 2014-09-08
19:57:21.073 SimpleList[49442:60b] * Terminating app due to uncaught
exception 'NSInternalInconsistencyException', reason: 'Auto Layout
still required after executing -layoutSubviews. UITableView's
implementation of -layoutSubviews needs to call super
I'm not sure what I'm doing wrong. I've also tried setting constraints for the self.tview.tableFooterView wrt the tempFooter (pinning against all 4 sides) but that doesn't change the crash. I also call:
[self.tview.tableFooterView layoutSubviews];
but that doesn't help either.
Any ideas what I'm doing wrong?
You shouldn't set translatesAutoresizingMaskIntoConstraints to NO for table views, their cells, or table header and footer views (or for your controller's self.view for that matter). It's ok to use constraints for views inside those views (the content view of cells or the header and footer views). I added a simplified version of your code to viewDidLoad, and it worked as expected. Note that you need to give the footer view a height and an x position (the y position and the width are ignored),
UIView *tempFooter = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 50)];
UILabel *atext = [[UILabel alloc] init];
atext.translatesAutoresizingMaskIntoConstraints = NO;
atext.numberOfLines = 0;
[atext setText:#"Add Some Text"];
atext.textAlignment = NSTextAlignmentCenter;
atext.font = [UIFont italicSystemFontOfSize:14];
atext.textColor = [UIColor grayColor];
atext.backgroundColor = [UIColor clearColor];
atext.lineBreakMode = NSLineBreakByWordWrapping;
[tempFooter addSubview:atext];
NSDictionary *dict = NSDictionaryOfVariableBindings(atext);
[tempFooter addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-10-[atext]-10-|" options:0 metrics:nil views:dict]];
[tempFooter addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[atext]|" options:0 metrics:nil views:dict]];
self.tableView.tableFooterView = tempFooter;
I'm not sure what you were trying to accomplish with your constraint, tvatt3. The multiplier (you have 5) doesn't do anything with a top or left constraint since the value of those attributes is 0. I used the visual constraints, rather than the method you used, but when I used your code to add the constraints, it worked also .
Interestingly, changing the Autoresizing mask in the xib solved my problem.
Constraints not working:
Working:
I was able to make dynamically auto layout table footer width and height by implementing #rdelmar solution for constraints and adding these lines below. Same solution applies to table header view.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let tableFooter = tableView.tableFooterView {
let maxSize = CGSize(width: self.tableView.bounds.size.width,
height: CGFloat.greatestFiniteMagnitude)
let size = tableFooter.systemLayoutSizeFitting(maxSize)
let height = size.height
var headerFrame = tableFooter.frame
// If we don't have this check, viewDidLayoutSubviews() will get
// repeatedly, causing the app to hang.
if height != headerFrame.size.height {
headerFrame.size.height = height
tableFooter.frame = headerFrame
tableView.tableFooterView = tableFooter
}
}
}
P. S. I prefer Interface Builder for constraints setup
hope this will help you
I am not sure of this but it help me in my project, first add every thing in contentView (all the subview and the constraint)
[self.contentView addSubview:atext]
and then comes to code part
NSLayoutConstraint *tvatt1 = [NSLayoutConstraint constraintWithItem:tempFooter
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:atext attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:10.0];
code saying for left attribute
temfooter(must be equal) = 1.0*atext +10.0
i.e
temfooter must be equal to the modified atext , so need to make sure that your constraints are not wrong.
In my main view, I do some gesture action causing some new view to be shown. At this time I want to dim the entire background (except this new view) as a good UI practice. In HTML, Javascript it looks like so - How do I get this same effect in iOS ?
Lay a UIView over the background, set its background to [UIColor blackColor] and set its opacity to like 0.6. Then add that UIView to the parent view.
This'll dim out the background AND intercept any taps to background controls.
While Dan's suggestion is on target, you cannot use a modal view controller in this case because a typical modal covers the entire screen, blocking the parent view controller, even if you have the background of your view as transparent (you'll see whatever you have in the application's UIWindow).
If you are doing this on the iPad there are 2 modal presentation styles (UIViewModalPresentationStylePageSheet and UIViewModalPresentationStyleFormSheet) that can do something similar, but those will not work on an iPhone or iPod Touch.
Add the "shadow" view, with the dark background and partial opacity and whatever view you want to be in the foreground, to the view controller's view directly. You can animate them in using standard UIView animation blocks, or CoreAnimation.
Another note, if you want to intercept touches to that shadow view you can either make it a giant button, subclass UIView to override one of the touch methods like touchesEnded: or change it to a UIControl which can receive touch events like a UIButton, but without the extra options for text, shadows, states etc.
The above two answers work if the new view has it's own 'background', i.e. has no transparency. The problem that led me here cannot be solved that way though, what I was trying to do is this: I'm running an AVCaptureSession on my screen with the usual AVCaptureVideoPreviewLayer to display video in real time. I want to select a part of the screen (to later do something with only this part), and when this part is selected want to dim the rest of the video preview. This is what it looks like:
This is how I solved this: A new view is created depending on where the screen is touched and added as a subview of the video preview view. Then, I create 4 additional, non-overlapping rectangular subviews with the before-mentioned background color and opacity to cover the rest of the screen. I explicitly need all these views not to be subviews of the main view, because I need the ability to touch the screen somewhere else and change the selected area.
I'm sure there are more elegant ways to solve this, so if someone knows how please comment...
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController<UIGestureRecognizerDelegate>
{
UIView *mainView;
UIView *dimBackgroundUIView;
UIView *customView;
UIButton *btn;
}
#end
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
mainView = [[UIView alloc] init];
mainView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:mainView];
[self addConstraintsForMainView];
btn = [[UIButton alloc] initWithFrame:CGRectMake(200, 200, 100, 30)];
[btn setTitle:#"Button" forState:UIControlStateNormal];
[btn setBackgroundColor:[UIColor blueColor]];
[btn addTarget:self action:#selector(showCustomView_Action:) forControlEvents:UIControlEventTouchUpInside];
[mainView addSubview:btn];
dimBackgroundUIView = [[UIView alloc] init];
dimBackgroundUIView.backgroundColor = [UIColor blackColor];
dimBackgroundUIView.alpha = 0.2;
}
-(void)removeCustomViewsFromSuperView {
if(dimBackgroundUIView)
[dimBackgroundUIView removeFromSuperview];
if(customView)
[customView removeFromSuperview];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)showCustomView_Action:(id)sender {
customView = [[UIView alloc] initWithFrame:CGRectMake(100, 200, 80, 80)];
customView.backgroundColor = [UIColor redColor];
[self.view addSubview:dimBackgroundUIView];
[self addConstraintsForDimView];
[self.view addSubview:customView];
UITapGestureRecognizer *tapped = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(removeCustomViewsFromSuperView)];
tapped.delegate=self;
tapped.numberOfTapsRequired = 1;
[customView addGestureRecognizer:tapped];
}
-(void) addConstraintsForDimView {
[dimBackgroundUIView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:dimBackgroundUIView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:dimBackgroundUIView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:dimBackgroundUIView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:dimBackgroundUIView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]];
}
-(void) addConstraintsForMainView {
[mainView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:mainView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:mainView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:mainView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:mainView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]];
}
#end
note: you can optimise the code by using graphical interface on Xcode but i had to write code programmatically to be able to test it
so the idea is to:
create UIView name it what you want (dimBackgroundUIView) than set
the backgroundcolor as Black and set alfa to 0.1 or 0.2 or....
add this 2 line of code when you need to dim the background:[self.view addSubview:dimBackgroundUIView];
[self addConstraintsForDimView];
and add this 2 line of code when you want to remove the dimming
background: if(dimBackgroundUIView) [dimBackgroundUIView removeFromSuperview];