How can I show multiple UIViews by accessing their isHidden property? - swift

I want to show multiple UIViews in a collection cell based on the logic statements below.
This function is for a Calendar view in my tabbar application. I'm reading events from a database on Firebase Firestore and saving it in calData.
func configureEventDotFor(cell: CalendarCell, cellState: CellState) {
let dateString = self.globalFormatter.string(from: cellState.date)
for event in self.calData.events! {
let eventDateString = self.globalFormatter.string(from: event.startDate)
if dateString != eventDateString {
cell.holidayBar.isHidden = true
cell.birthdayBar.isHidden = true
cell.defaultBar.isHidden = true
} else if event.category == "birthday" {
cell.birthdayBar.isHidden = false
} else if event.category == "holiday" {
cell.holidayBar.isHidden = false
} else if event.category == "default" {
cell.defaultBar.isHidden = false
} else {
return
}
}
}
There are four events in calData, 2 default, 1 holiday, and 1 birthday. The birthday and holiday events are on the same day and therefore both birthdayBar and holidayBar should be visible for that day. I also expect to see a defaultBar for the other two events however only the holidayBar is visible when I run the app.

The problem is that you appear to be trying to mess directly with the subviews of the cells of a UICollectionView. Don't do that. Just have the shown/hidden state of each subview be set in the cellForItemAt method, based on some easily accessible state (typically the contents of your data model). If the state changes, simply reload the collection view.

Related

Using UserDefaults in viewWillAppear

I'm setting up the Settings page (in SettingsView class) where users can set «On» / «Off» for the background parallax effect. The selection is saved in UserDefaults().string(forKey: "parallaxStatus"). In the viewWillAppear of ViewController class, I checked the parallaxStatus. If the status of the parallax effect is «On», then this effect is displayed. If the status is «Off», then nothing should happen.
The problem appeared when parallaxStatus changed from «On» to «Off» In this case, the parallax effect still displayed before I reload the View. But if parallaxStatus changed from «Off» to «On», the function works well without reloading the View.
Bellow is the code of viewWillAppear function. Thanks for any help or hint.
override func viewWillAppear(_ animated: Bool) {
let parallaxStatus = UserDefaults().string(forKey: "parallaxStatus")
if parallaxStatus == "On" {
let min = CGFloat(-40)
let max = CGFloat(40)
let xMotion = UIInterpolatingMotionEffect(keyPath: "layer.transform.translation.x", type: .tiltAlongHorizontalAxis)
xMotion.minimumRelativeValue = min
xMotion.maximumRelativeValue = max
let yMotion = UIInterpolatingMotionEffect(keyPath: "layer.transform.translation.y", type: .tiltAlongVerticalAxis)
yMotion.minimumRelativeValue = min
yMotion.maximumRelativeValue = max
let motionEffectGroup = UIMotionEffectGroup()
motionEffectGroup.motionEffects = [xMotion,yMotion]
bgImage.addMotionEffect(motionEffectGroup) } else { }
}
1- You should use a bool value in userDefaults
UserDefaults.standard.bool(forKey: "parallaxStatusOn") // default is false
2- viewWillAppear is called when you dimiss a presented / poped vc so in your case , you use a settingsView not vc verify it's being called by other KVO or any event driven notifier
3- if the state is on and changed to off , verify you remove the motion effects with if the vc is still appeared ( not deallocated btw do it in else of check )
bgImage.motionEffects.forEach { bgImage.removeMotionEffect($0) }

How to tap on a button inside a CollectionView cell which is not visible on screen

I am working on UITests using XCode. I have multiple CollectionView cells.
When I perform Count in the collectionView it shows the certain count.
I can able to access first two cells but coming to the 3rd cell as 3(depends on device size). It says that specific button I am looking for in 3rd Cell as exists.
But isHittable is false.
Is there any way I can tap on the button on the 3rd Cell.
I have tried using the extension for forceTapElement() which is available online, it didn’t help.
Extension Used:
extension XCUIElement{
func forceTapElement(){
if self.isHittable{
self.tap()
}else{
let coordinate: XCUICoordinate = self.coordinate(withNormalizedOffset: .zero)
coordinate.tap()
}
}
}
Tried to perform swipeUp() and access the button. it still shows isHittable as false
The only way I've found is to swipe up untile the isHittable will be true.
app.collectionViews.cells.staticTexts["TEST"].tap()
Thread.sleep(forTimeInterval: 3)
let collectionView = app.otherElements.collectionViews.element(boundBy: 0)
let testAds = collectionView.cells
let numberOfTestAds = testAds.count
if numberOfTestAds > 0 {
let tester = collectionView.cells.element(boundBy: 2).buttons["ABC"]
for _ in 0..<100 {
guard !tester.isHittable else {
break;
}
collectionView.swipeUp()
}
}
Please note that the swipeUp() method will only move few pixels. If you want to use more comprehensive methods you can get AutoMate library and try swipe(to:untilVisible:times:avoid:from:):
app.collectionViews.cells.staticTexts["TEST"].tap()
Thread.sleep(forTimeInterval: 3)
let collectionView = app.otherElements.collectionViews.element(boundBy: 0)
let testAds = collectionView.cells
let numberOfTestAds = testAds.count
if numberOfTestAds > 0 {
let tester = collectionView.cells.element(boundBy: 2).buttons["ABC"]
collectionView.swipe(to: .down, untilVisible: tester)
// or swipe max 100 times in case 10 times is not enough
// collectionView.swipe(to: .down, untilVisible: tester, times: 100)
}

UITable UITextField not displaying data correctly SWIFT 3.0

My application code is pretty lengthly so I would like to try and get this problem resolved without having to show a bunch of code....but here is what is going on..
I have a tableview with dynamic cells all set up just fine. There is a textfield in the cell. When data is entered into the textfield, it is then sent to a label within that same cell. This happen when a "done" button is pressed. This all works just find until I have about 6 rows added to the table. At that point, when the "done" button is pressed, the data gets all mixed up. Sometimes the data is sent to the wrong cell label and other times it doesn't send at all. This only happens when I get to about 6 rows. I am very confused. The cells are displayed based on a Core Data entity.count...the textfield has the delegate set as itself....I have no clue what could be causing this...only after 6 rows!?
Thank you in advance for your help...like I said, I would like to try and avoid pasting my code here but if I have to provide some I will or I can talk you through what is going on in my code...
EDIT
Here is some of my code in cellForRowAt that deals with the textFields in the cell content
if searchResults.count > 1 {
if showInGrams {
if foods[indexPath.row].units != "g" && foods[indexPath.row].units != "grams" && foods[indexPath.row].units != "Grams" {
if cell.changeServingTextField.text == "" {
cell.labelServing.text = String(format: "%0.2f", (100 / foods[indexPath.row].coefficient) * searchResults[indexPath.row].doubleValue)
cell.labelUOM.text = "grams"
} else {
if cell.changeServingSegmentedControl.selectedSegmentIndex == 0 {
cell.labelServing.text = String(format: "%0.2f", Double(cell.changeServingTextField.text!)!)
cell.labelUOM.text = cell.changeServingSegmentedControl.titleForSegment(at: 0)
//show default values
} else {
cell.labelServing.text = String(format: "%0.2f", Double(cell.changeServingTextField.text!)!)
cell.labelUOM.text = cell.changeServingSegmentedControl.titleForSegment(at: 1)
}
}
} else {
if cell.changeServingTextField.text == "" {
cell.labelServing.text = String(format: "%0.2f", searchResults[indexPath.row].doubleValue * foods[indexPath.row].serving)
cell.labelUOM.text = "grams"
}
}
} else {
if cell.changeServingTextField.text == "" {
cell.labelServing.text = String(format: "%0.2f", searchResults[indexPath.row].doubleValue * foods[indexPath.row].serving)
cell.labelUOM.text = foods[indexPath.row].units
} else {
if cell.changeServingSegmentedControl.selectedSegmentIndex == 0 {
cell.labelServing.text = String(format: "%0.2f", Double(cell.changeServingTextField.text!)!)
cell.labelUOM.text = cell.changeServingSegmentedControl.titleForSegment(at: 0)
} else {
cell.labelServing.text = String(format: "%0.2f", Double(cell.changeServingTextField.text!)!)
cell.labelUOM.text = cell.changeServingSegmentedControl.titleForSegment(at: 1)
}
}
}
I hope this helps, thank you
What you are seeing is a symptom of UITableView's reuse strategy. May be what you want to do is store the information entered in your UITextField in a collection and then every time the tableView "reloads", you pick it out from that collection for the current indexPath and load it.
A line like this is never going to work:
if cell.changeServingTextField.text == ""
The problem is that cells are reused. Thus, in its previous life, if the text field had text, it still has text and the test will fail.
Instead, your logic needs to be based entirely on what row this is and what the data corresponding to that row is. And it needs to cover every case, because the cell can be reused.
Things would be clearer if you moved most of this logic into the cells themselves. You can start off by making all your cell's IBOutlets fileprivate and assigning a model object or dictionary as a property of the cell. That way you're separating the logic for creating the cells from their display - and will prevent you using cell's outlets to determine state.
While this might not solve your problem directly, separating the logic might clarify where to look.

Quickest way to check multiple bools and changing image depending on that bool

I have multiple achievements that the user can achieve during the game. When the user goes to the achievement VC, he should see an image when the achievement is accomplished.
I am now using this code in the viewDidLoad function:
if unlockedAchievement1 == true
{
achievement1Text.textColor = UIColor.greenColor()
achievement1Image.image = UIImage(named: "achievement1Unlocked")
}
if unlockedAchievement2 == true
{
achievement2Text.textColor = UIColor.greenColor()
achievement2Image.image = UIImage(named: "achievement2Unlocked")
}
This would work, but it takes a lot of time to copy paste this overtime. How can I shorten this? Should I make a class? I read about for in loops, but I did not quite understood that. Any help is welcome!
I feel like I have seen this before. But anyway...
//use array of bools. First declare as empty array.
//Use Core data to achieve saving the Booleans if you need to.
let achievements:[Bool] = []
let achievementsImage:[UIImage] = []
let achievementsText:[UITextView] = []
func viewDidLoad() {
//Say you have 5 achievements.
for index in 0 ..< 5 {
achievements.append(false)
achievementsImage.append(UIImage(named: "achievementLocked"))
achievementText.append(UITextView())
//Then you can edit it, such as.
achievementText[index].frame = CGRect(0, 0, 100, 100)
achievementText[index].text = "AwesomeSauce"
self.view.addSubview(achievementText[index])
achievementText[index].textColor = UIColor.blackColor()
}
}
//Call this function and it will run through your achievements and determine what needs to be changed
func someFunction() {
for index in 0 ..< achievements.count {
if(achievements[index]) {
achievements[index] = true
achievementsImage[index] = UIImage(named: String(format: "achievement%iUnlocked", index))
achievementsText[index].textColor = UIColor.greenColor
}
}
}
I'd recommend making your unlockedAchievement1, unlockedAchievement2, etc. objects tuples, with the first value being the original Bool, and the second value being the image name. You'd use this code:
// First create your data array
var achievements = [(Bool, String)]()
// Then you can make your achievements.
// Initially, they're all unearned, so the Bool is false.
let achievement1 = (false, "achievement1UnlockedImageName")
achievements.append(achievement1)
let achievement2 = (false, "achievement2UnlockedImageName")
achievements.append(achievement2)
// Now that you have your finished data array, you can use a simple for-loop to iterate.
// Create a new array to hold the images you'll want to show.
var imagesToShow = [String]()
for achievement in achievements {
if achievement.0 == true {
imagesToShow.append(achievement.1)
}
}

Getting notified when an NSView comes to the foreground

I have a NSTableView inside the second tab of a NSTabView. The table uses some custom event code that takes care of buttons in the table being clicked are recognised. The problem is that even if I have the first or third tab of the tab view open (i.e. table view is not visible), these events are being intercepted (so if I have buttons on 1./3. tab they don't work).
To solve this I added a flag to my custom table view class to only execute the custom event code when the flag is true and if not just return the original event.
Now I'm wondering how do I tell if the table view is in the front (i.e. tab no. 2 is open)? Is there any elegant way to check for this or do I need to get this from the tab bar directly?
In case it matters, here's my custom table code...
class ButtonTableView : NSTableView
{
var isAtForeground:Bool = false;
override init(frame frameRect:NSRect) {
super.init(frame: frameRect);
}
required init?(coder:NSCoder) {
super.init(coder: coder);
addEventInterception();
}
/// Get the row number (if any) that coincides with a specific point - where the point is in window coordinates.
func rowContainingWindowPoint(windowPoint:CGPoint) -> Int? {
var rowNum:Int?;
var tableRectInWindowCoords = convertRect(bounds, toView: nil);
if (CGRectContainsPoint(tableRectInWindowCoords, windowPoint))
{
let tabPt = convertPoint(windowPoint, fromView: nil);
let indexOfClickedRow = rowAtPoint(tabPt);
if (indexOfClickedRow > -1 && indexOfClickedRow < numberOfRows)
{
rowNum = indexOfClickedRow;
}
}
return rowNum;
}
func addEventInterception() {
NSEvent.addLocalMonitorForEventsMatchingMask(.LeftMouseDownMask, handler:
{
(theEvent) -> NSEvent! in
/* Don't bother if the table view is not in the foreground! */
if (!self.isAtForeground) { return theEvent; }
var e:NSEvent? = theEvent;
let p:NSPoint = theEvent.locationInWindow;
/* Did the mouse-down event take place on a row in the table? */
if let row = self.rowContainingWindowPoint(p)
{
let localPoint = self.convertPoint(p, fromView: nil);
/* Get the column index that was clicked in. */
let col = self.columnAtPoint(localPoint);
/* Get the table cell view on which the mouse down event took place. */
let tcv = self.viewAtColumn(col, row: row, makeIfNecessary: false) as! NSTableCellView;
/* Did the click occur on the table view? */
let tableBoundsInWindowCoords:NSRect = self.convertRect(self.bounds, toView: nil);
if (CGRectContainsPoint(tableBoundsInWindowCoords, p))
{
/* If clicked anywhere in the table cell view, only allow button areas to pass. */
/* subView is the NSTableCellView's last subview. */
var subView = tcv.subviews.last! as! NSView;
let subViewBoundsInWindowCoords:NSRect = subView.convertRect(subView.bounds, toView: nil);
/* Did the click occur on the subview part of the view? */
if (CGRectContainsPoint(subViewBoundsInWindowCoords, p))
{
/* Only allow button subviews to be clicked. */
if let button = subView as? NSButton
{
// Create a modified event, where the <location> property has been
// altered so that it looks like the click took place in the
// NSTableCellView itself.
let newLocation = tcv.convertPoint(tcv.bounds.origin, toView: nil);
e = theEvent.cloneEventButUseAdjustedWindowLocation(newLocation);
return e;
}
}
/* If we've reched here, the user clicked anywhere on the table but not one of the buttons. */
return nil;
}
}
return e;
});
}
}