I'm trying to add an animation to fade out a button when it is tapped. The code I am using at the moment just waits a few seconds and then fades out the button almost instantly. I was wondering how I can start to fade out the button as soon as it is tapped and make the fade last for around a second?
This is the code that I am using at the moment:
UIView.animateWithDuration(1.5,
delay: 0,
options: UIViewAnimationOptions.CurveLinear,
animations: {
self.valueEnter.alpha = 0
}, completion: nil)
The issue was that for fading the button out, rather than using
self.valueEnter.alpha = 100
I should have been using
self.valueEnter.alpha = 1
I didn't realise that the range was between 0 and 1 rather than 0 and 100.
Select button in the storyboard/nib
Open Connections Inspector
Connect button Touch Down and Touch Up Inside events:
... to the following methods:
#IBAction func fadeButtonTouchDown(sender: UIButton) {
sender.highlighted = false
UIView.animateWithDuration(1.5,
delay: 0,
options: .CurveLinear & .AllowUserInteraction & .BeginFromCurrentState,
animations: {
sender.alpha = 0
}, completion: nil)
}
#IBAction func fadeButtonTouchUpInside(sender: UIButton) {
sender.highlighted = false
}
Remarks: methods should be added to your view controller.
Related
I've made a custom control based around a Canvas. It uses two .pointerInput modifiers, one to detect click and one to detect drag so the user can toggle a column of 50 buttons, either by clicking on one at a time or dragging across a number of them to set them all at once. It works well, and now I'd like to have a horizontally scrollable Row containing a number of these. The immediate problem is that the Row, when the .horizontalScroll modifier is applied, swallows vertical movement as well, and even taps, so although I can scroll through a number of controls I'm no longer able to interact with them.
The only example I can find that's similar is the nested scrolling in the Gestures documentation, but that's between two controls using scrolling, and although the outer control is clearly not preventing the inner control receiving events it's not clear how to apply it in my case.
Without pasting huge quantities of code, I'm defining the Row by
#Composable
fun ScrollBoxes() {
Row(
modifier = Modifier
.background(Color.LightGray)
.fillMaxSize()
.horizontalScroll(rememberScrollState())
) {
repeat(20) {
Column {
Text(
"Item $it", modifier = Modifier
.padding(2.dp)
.width(500.dp)
)
JetwashSlide(
model = JetwashSlideViewModel()
)
}
}
}
}
and the modifier of the Canvas is my custom control is set up as
modifier
.pointerInput(Unit) {
detectDragGestures(
onDragStart = { ... }
},
onDragEnd = { ... },
onDrag = { change, dragAmount ->
change.consumeAllChanges();
...
}
}
)
}
.pointerInput(Unit) {
detectTapGestures(
onPress = { it ->
...
}
)
}
A crude approach would be to have a scrollable row of labels and use the presently selected label to determine which full width custom control is presently visible. This wouldn't be as aesthetically pleasing as having the controls scrolling horizontally. Does anyone have an idea how this could be achieved?
Ok, my starting point was this tutorial on how to make a scrolling pager from scratch;
https://fvilarino.medium.com/creating-a-viewpager-in-jetpack-compose-332d6a9181a5
Using that, you can get a horizontally scrolling row of instances of a control. The problem is that I wanted horizontal swipes to be handled by the pager, and taps and vertical swipes to be handled by the custom control, and although the custom control gets the gestures if made over the control, it couldn't pass them up to the pager if they were horizontal swipes, so you could only scroll using the gaps between the custom controls. I expected that the control could use .detectVerticalDragGestures and .detectTapGestures and the pager could use .detectHorizontalDragGestures, but it wasn't that simple.
I ended up pulling code from the Jetpack source into my own code so I could modify it to produce my own gesture detector which captures vertical scroll and tap events but does not capture horizontal scroll;
suspend fun PointerInputScope.detectVerticalDragOrTapGestures(
onDragStart: (Offset) -> Unit = { },
onDragEnd: () -> Unit = { },
onDragCancel: () -> Unit = { },
onVerticalDrag: (change: PointerInputChange, dragAmount: Float) -> Unit,
onClick: ((Offset) -> Unit) = { },
model: JetwashSlideViewModel
) {
forEachGesture {
awaitPointerEventScope {
model.inhibitGesture = false
val down = awaitFirstDown(requireUnconsumed = false)
//val drag = awaitVerticalTouchSlopOrCancellation(down.id, onVerticalDrag)
val drag = awaitTouchSlopOrCancellation(
down.id
) { change: PointerInputChange, dragAmount: Offset ->
if (abs(dragAmount.y) > abs(dragAmount.x)) {
//This is a real swipe down the slide
change.consumeAllChanges()
onVerticalDrag(change, dragAmount.y)
}
}
if (model.inhibitGesture) {
onDragCancel()
} else {
if (drag != null) {
onDragStart.invoke(drag.position)
if (
verticalDrag(drag.id) {
onVerticalDrag(it, it.positionChange().y)
}
) {
onDragEnd()
} else {
onDragCancel()
}
} else {
//click.
if (!model.inhibitGesture)
onClick(down.position)
}
}
}
}
}
Now in the pager, it was using .horizontalDrag. This is fine if you can actually drag purely horizontally or purely vertically, but you can't and swipes that were intended to be vertical on the inner control often had a tiny bit of horizontal motion which caused the pager to intercept it. So in the pager, I also had to copy and modify some code to make my own .horizontalDrag;
suspend fun AwaitPointerEventScope.horizontalDrag(
pointerId: PointerId,
onDrag: (PointerInputChange) -> Unit
): Boolean = drag(
pointerId = pointerId,
onDrag = onDrag,
motionFromChange = { if (abs(it.positionChangeIgnoreConsumed().x) > 10) it.positionChangeIgnoreConsumed().x else 0f },
motionConsumed = { it.positionChangeConsumed() }
)
This only triggers if the horizontal component of the movement is larger than 10px.
Finally, since a horizontal scroll may also have some element of vertical which could affect the inner controls, in my onPagerScrollStart and onPagerScrollFinished callbacks I set and clear a flag in the model, inhibitGesture which causes the inner control to disregard gestures that it happens to get while the pager is in the process of being scrolled.
I currently have an app written in objc and swift.
I would like to implement a custom popup interstitial ad that runs for a few seconds and then presents an X to close the popup and continues doing so every couple minutes unless the
Remove Ad's in-ap purchase has been purchased.
I already have the in-app purchases setup. I would really appreciate the help or a demo/sample would be fantastic!
Create a separate controller with an image view which will popup according to the timer set in app delegate. close X should also have a timer
func applicationDidEnterBackground(application: UIApplication) {
if (!isShowingAd && adTimer != nil) {
adTimerTimeLeft = adTimer!.fireDate.timeIntervalSinceNow
adTimer!.invalidate()
}
if adUpdateTimer != nil
{
adUpdateTimerTimeLeft = 60 * 60 //one hour
self.adUpdateTimer!.invalidate()
}
}
//setting timer, This way you can set timer
var adTimer: NSTimer?
timer = NSTimer.scheduledTimerWithTimeInterval(10.0, target: self, selector: #selector(AppDelegate.updateAd), userInfo: nil, repeats: true)
timer?.fire()
So this is kind of a noobish question but I just can't figure out a very simple way to detect currently focused item's indexPath.
I looked around hoping to see something very easy like collectionView.indexPathOfCurrentlyFocusedItem but didn't find anything remotely close.
So I digged around and tried to find something similar at UIFocusEnvironment, UIFocusUpdateContext trying to find the desired property but failed.
So, the only solution I can come up with is just iterating through all visible cells and finding a cell with focused property set to true.
So is there a more simple and elegant way to find the currently focused item's indexPath? (Except tracking it through delegate method and saving it in view controller's property)
You can use UIScreen property focusedView as followed for this:
if let focusedCell = UIScreen.main.focusedView as? UICollectionViewCell {
if let indexPath = collectionView.indexPath(for: focusedCell) {
print("IndexPath is \(indexPath)")
}
}
Use didUpdateFocusInContect - UICollectionViewDelegate
func collectionView(collectionView: UICollectionView, didUpdateFocusInContext context: UICollectionViewFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
if collectionView == self.collectionView {
print(context.nextFocusedIndexPath)
}
}
This wil return the indexPath of the cell that is going to be focused, you could also try:
context.previouslyFocusedIndexPath
Depends what you're trying to do.
So, your target is to do something when you get and lose focus particularly on a cell under tvOS. The catch is you're moving around other UI elements, and therefore the context could be different. You have to change in this context only those UIs that you have to care of.
The right place to make your implementation is func didUpdateFocusInContext(), like this:
override func didUpdateFocusInContext(
context: UIFocusUpdateContext,
withAnimationCoordinator coordinator: UIFocusAnimationCoordinator
) {
coordinator.addCoordinatedAnimations({
if let cell = context.previouslyFocusedView as? UICollectionViewCell {
cell.layer.borderWidth = 2
}
if let cell = context.nextFocusedView as? UICollectionViewCell {
cell.layer.borderWidth = 5
}
},
completion: nil)
}
Now we're using the focus coordinator to apply our logic:
When the previously focused item is UICollectionViewCell then you have to release the focus to the next item. You shouldn't care what is the next item because it could be a collection cell or not. For fun, in this case, let's change the border to 2. This value could be set by default.
When the next focused item is UICollectionViewCell then you've to handle it is a similar way, or it will become a mess... So, let's change the border to 5.
As you can see, didUpdateFocusInContext() provides a generic approach for all views within your current visual context. You can apply the same approach for other UI elements.
Have a fun with tvOS...
Here's how I accomplished this in shouldUpdateFocusInContext
Solution
override func shouldUpdateFocusInContext(context: UIFocusUpdateContext) -> Bool {
// The magic is in the next two lines
let cell: UICollectionViewCell = context.nextFocusedView as! UICollectionViewCell
let indexPath: NSIndexPath? = self.collectionView.indexPathForCell(cell)
print(indexPath)
// <NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}
return true
}
I have done a View in CollectionView with CustomLayout. In iOS6 it worked great but iOS7 it throws an exception like this.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'layout attributes for supplementary item at index path ( {length = 2, path = 0 - 0}) changed from CustomSupplementaryAttributes: 0xd1123a0 index path: (NSIndexPath: 0xd112580 {length = 2, path = 0 - 0}); element kind: (identifier); frame = (0 0; 1135.66 45); zIndex = -1; to CustomSupplementaryAttributes: 0xd583c80 index path: (NSIndexPath: 0xd583c70 {length = 2, path = 0 - 0}); element kind: (identifier); frame = (0 0; 1135.66 45); zIndex = -1; without invalidating the layout'
iOS 10
At iOS 10, a new feature is introduced, it is Cell Prefetching. It will let dynamic position of SupplementaryView crash. In order to run in the old behavior, it needs to disable prefetchingEnabled. It's true by default at iOS 10.
// Obj-C
// This function is available in iOS 10. Disable it for dynamic position of `SupplementaryView `.
if ([self.collectionView respondsToSelector:#selector(setPrefetchingEnabled:)]) {
self.collectionView.prefetchingEnabled = false;
}
// Swift
if #available(iOS 10, *) {
// Thanks #maksa
collectionView.prefetchingEnabled = false
// Swift 3 style
colView.isPrefetchingEnabled = false
}
I hate this problem. I spend 2 days for this problem.
A reference about Cell Pre-fetch #iOS 10.
iOS 9 and before ...
#Away Lin is right.. I solve the same problem by implementing that delegate method.
My Custom UICollectionViewLayout will modify the attributes in layoutAttributesForElementsInRect. The section position is dynamic, not static. So, I obtain warnings about the layout attributes for supplementary item at index path ... changed from ... to .... Before the changes, invalideLayout related methods should be called.
And, after implementing this delegate method to return true, the method invalidateLayoutWithContext: will be called when scrolling the UICollectionViewLayout. By default, it returns false.
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
From Apple Docs
Return Value
true if the collection view requires a layout update or false if the
layout does not need to change.
Discussion The default implementation of this method returns false.
Subclasses can override it and return an appropriate value based on
whether changes in the bounds of the collection view require changes
to the layout of cells and supplementary views.
If the bounds of the collection view change and this method returns
true, the collection view invalidates the layout by calling the
invalidateLayoutWithContext: method.
Availability Available in iOS 6.0 and later.
And more ...
A nice example project on GitHub, for custom UICollectionViewLayout.
You need to invalidate the existing layout before updating, see the end of the error message:
without invalidating the layout'
[collectionViewLayout invalidateLayout];
Apple Documentation for UICollectionViewLayout
I had the same exception: in iOS 7, you need now to override the inherited isEqual: in your UICollectionViewLayoutAttributes subclass as stated in Apple documentation here.
I solved my problem by override the method at the subclase of UICollectionViewFlowLayout:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBound
return YES
I'm not entirely certain how or why, but this appears to be fixed in iOS 12, supporting both supplementary view resizing and prefetching. The trick for me was to make sure things are happening in the correct order.
Here is a working implementation of a stretchable header view. Notice the implementation of the header resizing happening in layoutAttributesForElements(in rect: CGRect):
class StretchyHeaderLayout: UICollectionViewFlowLayout {
var cache = [UICollectionViewLayoutAttributes]()
override func prepare() {
super.prepare()
cache.removeAll()
guard let collectionView = collectionView else { return }
let sections = [Int](0..<collectionView.numberOfSections)
for section in sections {
let items = [Int](0..<collectionView.numberOfItems(inSection: section))
for item in items {
let indexPath = IndexPath(item: item, section: section)
if let attribute = layoutAttributesForItem(at: indexPath) {
cache.append(attribute)
}
}
}
if let header = layoutAttributesForSupplementaryView(ofKind: StretchyCollectionHeaderKind, at: IndexPath(item: 0, section: 0)) {
cache.append(header)
}
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let visibleAttributes = cache.filter { rect.contains($0.frame) || rect.intersects($0.frame) }
guard let collectionView = collectionView else { return visibleAttributes }
// Find the header and stretch it while scrolling.
guard let header = visibleAttributes.filter({ $0.representedElementKind == StretchyCollectionHeaderKind }).first else { return visibleAttributes }
header.frame.origin.y = collectionView.contentOffset.y
header.frame.size.height = headerHeight.home - collectionView.contentOffset.y
header.frame.size.width = collectionView.frame.size.width
return visibleAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = super.layoutAttributesForItem(at: indexPath as IndexPath)?.copy() as! UICollectionViewLayoutAttributes
guard collectionView != nil else { return attributes }
attributes.frame.origin.y = headerHeight.home + attributes.frame.origin.y
return attributes
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: StretchyCollectionHeaderKind, with: indexPath)
}
override var collectionViewContentSize: CGSize {
get {
guard let collectionView = collectionView else { return .zero }
let numberOfSections = collectionView.numberOfSections
let lastSection = numberOfSections - 1
let numberOfItems = collectionView.numberOfItems(inSection: lastSection)
let lastItem = numberOfItems - 1
guard let lastCell = layoutAttributesForItem(at: IndexPath(item: lastItem, section: lastSection)) else { return .zero }
return CGSize(width: collectionView.frame.width, height: lastCell.frame.maxY + sectionInset.bottom)
}
}
}
P.S.: I'm aware the cache doesn't actually serve any purpose at this point :)
I had this problem too, because I had code that depended on the content size of the collection view. My code was accessing the content size via the collectionView!.contentSize instead of collectionViewContentSize.
The former uses the collectionView property of UICollectionViewLayout, while the latter uses the custom-implemented layout property. In my code, the first time the layout was asked for attributes, contentSize had not been set yet.
Select the CollectionView and Goto Attribute Inspector. Uncheck The Prefetching Enabled CheckBox. This is Fixed my Issue. Screenshot
I would like to load a few images to the Image object in QML. I want to do it one after another in a loop I think with some delay, because it should pretend to look like an animation. Im new in Qt and QML, so can anybody help me how to begin or somethin'? :)
The simplest solution, that works in QML 1.x and 2.0, is to use a Repeater and a Timer :
import QtQuick 2.0
Rectangle {
id: base;
width: 800;
height: 600;
property variant images : [
"image1.jpg",
"image2.jpg",
"image3.jpg",
"image4.jpg",
"image5.jpg"
];
property int currentImage : 0;
Repeater {
id: repeaterImg;
model: images;
delegate: Image {
source: modelData;
asynchronous: true;
visible: (model.index === currentImage);
}
}
Timer {
id: timerAnimImg:
interval: 500; // here is the delay between 2 images in msecs
running: false; // stopped by default, use start() or running=true to launch
repeat: true;
onTriggered: {
if (currentImage < images.length -1) {
currentImage++; // show next image
}
else {
currentImage = 0; // go back to the first image at the end
}
}
}
}
Should do it, and if you don't want the animation to restart when the last image is reached, just replace currentImage = 0; with stop(); in the timer's onTriggered.
Also, you should possibly have to tweak a little the Image delegate in the Repeater to give it the look you want (size, fill mode, position etc...).
If you are using QtQuick 2 then AnimatedSprite is the best way to do this: http://qt-project.org/doc/qt-5.0/qtquick/qtquick-effects-sprites.html
Otherwise, you could use a Timer or NumberAnimation to trigger changing the image source, but there could be unpredictable delays as images load for the first time (caching should solve this after the first loop). If you only have a few images, you could have three Images and cycle their visibility.