I am trying to create a very simplified layout, something like Stripe's iOS app has which you can see here: https://stripe.com/img/dashboard_iphone/screencast.mp4
At around 0:06s the table view is swiped up and it moves up to the top of the window.
Are there any simple instructions in Swift that show this design pattern I see it everywhere but no idea how to create it
Add a UIView
Subclass that UIView with a custom class
In you custom UIView, you'll need a couple of variables and a few overrides. Make sure that user interaction is enabled on the UIView in your storyboard, then in your custom class add:
var startPosition: CGPoint?
var originalHeight: CGFloat?
After that, add the following:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
startPosition = touch?.locationInView(self)
originalHeight = self.frame.height
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let endPosition = touch?.locationInView(self)
let difference = endPosition!.y - startPosition!.y
let newFrame = CGRectMake(self.frame.origin.x, self.frame.origin.y + difference, self.frame.width, self.frame.height - difference)
self.frame = newFrame
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
In UIScrollView.h:
// When the user taps the status bar, the scroll view beneath the touch which is closest to the status bar will be scrolled to top, but only if its `scrollsToTop` property is YES, its delegate does not return NO from `shouldScrollViewScrollToTop`, and it is not already at the top.
// On iPhone, we execute this gesture only if there's one on-screen scroll view with `scrollsToTop` == YES. If more than one is found, none will be scrolled.
var scrollsToTop: Bool // default is YES.
Initialize your scroll view delegate (unless you already have, probably in your viewDidLoad), and then set this to true.
Like this:
myScrollView.scrollsToTop = true
If you want to be sure your scroll view delegate allows this, check this method from UIScrollViewDelegate's protocol:
optional func scrollViewShouldScrollToTop(scrollView: UIScrollView) -> Bool // return a yes if you want to scroll to the top. if not defined, assumes YES
Please comment any concerns.
Related
I have been trying to implement an animation of sorts where a view rotates about a point when the user drags across the screen. I have implemented the part of it that controls the rotating (the part pertaining to this question). You can view all the code I have here if you would like to (it's short).
The code that controls the rotation is mainly in touchesBegan and touchesMoved:
var p = CGPoint.zero
// ...
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
p = (touches.first?.location(in: self))!
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let dev = p.x - (touches.first?.location(in: self).x)!
let angle = 2 * CGFloat(M_PI) - atan(dev / p.y)
self.transform = CGAffineTransform(rotationAngle: angle)
}
Now here is the problem: when my finger drags below the initial middle of the view (the middle before any rotation), it works perfectly fine; however, when my finger drags above the initial middle of the view, the view is rotated to the wrong amount, and it jumps back and forth between two positions really fast, so it looks like there is a copy of the view.
I don't know if it is not working because of my general math/logic or with my implementation.
Why does it not work correctly when I drag above the initial middle of the view, and how can I fix it?
I am trying to implement simple Image manipulation in Swift, I want to draw above the image of UIImageView.
So The interface will be as it is in the following picture:
when clicking on the Emoji downside, i want to drag it and drop it on the imageview, or only clicking will move it to the uiimageview,
Then it can be moved around inside the uiimageview and save the whole image to galery.
I could not find useful source about this on google.
so Where to start with this?
If the emoji is a UIImageView, you can implement touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?). The following is where mainImageView is the view in which you want to add the emoji.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = event?.allTouches()?.first
let touchLocation = touch?.locationInView(self.view)
if(CGRectContainsPoint(emojiImageView?.frame, touchLocation)) {
//your emoji imageView has been selected.
addImageviewToMainImageView(emojiImageView)
}
}
func addImageviewToMainImageview(imageView: UIImageView!) {
imageView.removeFromSuperView()
let origin = CGPoint(mainImageView.center.x , mainImageView.center.y)
let frame = CGRect(origin.x, origin.y, imageView.frame.size.width, imageView.frame.size.height)
imageView.frame = frame
mainImageView.addSubview(imageView)
}
If you want to move the emojiImageView around within the mainImageView, you should subclass UIView to implement touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) and touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) in a similar fashion.
1) Detect the touch with touchesBegan, determine if one of the mainImageViews subviews has been touched. Set a reference to this subview.
2) If a subview has been touched, touchesMoved will use that reference to determine the new location of the subview:
let touch = event?.allTouches()?.first
let touchLocation = touch?.locationInView(self)
selectedSubview.center = touchLocation
Use following code to combine 2 images, I am not adding code for drag and drop thing.
UIImage *mainImage = firstImage;
UIImage *smallImage = secondImage;
CGPoint renderingPoint = CGPointMake(50,50);
UIImage *outputImage = nil;
if (smallImage.size.width > mainImage.size.width || smallImage.size.height > mainImage.size.height)
{
return smallImage;
}
UIGraphicsBeginImageContext(firstImage.size);
[firstImage drawAtPoint:CGPointMake(0,0)];
[secondImage drawAtPoint:renderingPoint];
outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The outputImage here is output of 2 combined images. The renderingPoint is the Point at which you want to start drawing other image.
Just Add touches methods and get the point at which Touch ends, keeping that point as center point and by using smallImage's width, you can calculate its rendering point.
Also in keep in mind that here renderingPoint is with respect to original size of image, not the size of imageview, so make calculation accordingly to scale up or down to imageview.
I am trying to build similar controller to GMSPlacePicker.
I have Map View on background, then Table View with transparent header view. The problem is that all gestures (tap, pan) within header view are passed to table view. I wanna disable them, so all touches will go directly to map view.
I am able to do it If I set:
tableView.userInteractionEnabled = false
but now I am not able to scroll table view.
The question is how to disable all gesture only for header view, but keep getting them for table view.
Basically I wanna get the following behaviour: https://youtu.be/iSBbEZXDyGg
The trick was to create subclass of UITableView and override
func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView?
ANTableView.swift
import UIKit
class ANTableView: UITableView
{
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView?
{
let headerViewFrame = tableHeaderView!.convertRect(tableHeaderView!.frame, toView: self)
if CGRectContainsPoint(headerViewFrame, point) {
return nil
}
return self
}
}
You need to put your map inside the header view. Not behind it.
Say for example I have a white background, then on every time the view is touched, a blue box sized (60x60) is added to the screen. If i keep tapping all over the screen until the entire view becomes blue, how can I use code to notify the user using for example an alert controller saying that the view has now changed color completely.
if that made sense to anyone, I would appreciate any suggestions :)
As others have stated, the best approach is to hold a reference to a 2D array to correlate with the UI. I would approach this by first:
1) Create a subclass of UIView called overlayView. This will be the view overlay on top of your View Controllers' view. This subclass will override init(frame: CGRect). You may implement a NxM grid on initialization. You will also create and add the subviews as appropriate.
Example:
let boxWidth: CGFloat = 60
let boxHeight: CGFloat = 60
let boxFrame: CGRect = CGRectMake(0, 0, 600, 600)
override init(frame: CGRect) {
super.init(frame: boxFrame)
for i in 1 ... 10 { //row
for j in 1...10 { //column
let xCoordinate: CGFloat = CGFloat(CGFloat(i) * boxWidth)
let yCoordinate: CGFloat = CGFloat(CGFloat(j) * boxHeight)
let boxFrame: CGRect = CGRect(x: xCoordinate, y: yCoordinate, width: boxWidth, height: boxHeight)
var subView: UIView = UIView(frame: boxFrame)
subView.backgroundColor = UIColor.greenColor()
self.addSubview(subView)
}
}
}
2) Override touchesBegan(touches: NSSet, withEvent event: UIEvent) within overlayView. The implementation should look something like this:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch: UITouch = touches.anyObject() as UITouch
let touchLocation: CGPoint = touch.locationInView(self)
for subView in self.subviews {
if(CGRectContainsPoint(subView.frame, touchLocation)) {
subView.removeFromSuperview()
}
}
}
2a) You will also want to create a function to notify your 2D array that this subview as been removed. This will be called from within your if statement. In addition, this function will detect if the array is empty (all values set to false)
OR
2b) if you do not need a reference to the model (you do not care which boxes are tapped only that they're all gone), you can skip 2a and check if self.subviews.count == 0. If this is the case, you do not need a 2D array at all. If this condition passes, you can present an alert to the user as needed.
3) Create an overlayView in your main View Controller and add it as a subview.
I have a floating UIView header below a UINavigationBar. I have inserted it as a subview at index 0, so I can animate it away and in according to contentOffset of a UITableView.
However, because it's beyond the bounds of the UINavigationBar I cannot receive touch events in this view after adding a UITapGestureRecognizer to it. All touch events go to the UITableView below it.
Any ideas if it's possible to have a subview outside of the bounds of the navigation bar and receive touch events for it?
I did this because adding it as a subview of a UITableView, I'd have to set the Y origin based on the contentOffset while scrolling and this also makes animations very difficult since the Y origin changes during scroll, so I can't know where to animate it to.
It's similar to the header in the Facebook app with "Status", "Photo" and "Check In" buttons.
Thanks
You need to create custom UINavigationBar and reimplement pointInside:withEvent: to extend touchable area of navigation bar (add your view area):
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGRect extendedRect = self.frame;
extendedRect.size.height += MENU_HEADER_HEIGHT - self.frame.origin.y;
BOOL pointInside = NO;
if (CGRectContainsPoint(extendedRect, point)) {
pointInside = YES;
}
return pointInside;
}
Adding to the above answer. Here is what I did.
class CustomNavigationBar: UINavigationBar {
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
if(self.pointInside(point, withEvent: event)){
self.userInteractionEnabled = true
}else{
self.userInteractionEnabled = false
}
return super.hitTest(point, withEvent: event)
}
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
var extendedRect:CGRect = self.frame
extendedRect.size.height += 100.0 - self.frame.origin.y
var pointInside:Bool = false
if(CGRectContainsPoint(extendedRect, point)){
pointInside = true
}
return pointInside
}
}
As far as I know you can't receive touch events outside the bounds of a view. So, I see a couple options:
First, you could go with the option of adding it as a subview to the UITableView, as you said, then adjusting its Y origin based on the tableview's contentOffset. When you want to animate it, instead of animating it to a specific y position you could do something like this:
[UIView animateWithDuration:0.4
animations:^{
_myHeaderView.frame = CGRectOffset(_myHeaderView.frame, 0.0, -_myHeaderView.frame.size.height);
}];
Another option I could see is adding the header view on top of everything, but with its origin sitting just below the UINavigationBar. Then when you animate it you could just move it to be below the UINavigationBar (or even actually pass it to the UINavigationBar) and animate after that so that it slides up underneath.
Swift 4 solution:
class TappableNavigationBar: UINavigationBar {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
isUserInteractionEnabled = self.point(inside: point, with: event)
return super.hitTest(point, with: event)
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
var extendedRect = frame
extendedRect.size.height += 300.0 - frame.origin.y
return extendedRect.contains(point)
}
}