I have a view that contains some objects like this (the green area is my view):
Every time I press "item" a new color object gets added to this row of views, I want the "row" button to copy this row as it is and place it above this row when pressed, so I have two (or multiple) rows on top of each other. I made the action for row button like this:
let hc = contentView1
hc?.setFrameOrigin(NSPoint(x: 0, y: (positionY*50)))
positionY += 1
contentView2?.addSubview(hc!)
but every time I press that button, the same row just moves and does not copy, like this:
What am I doing wrong?
A view can have one superview and each time you add a view to a superview, it is removed from its previous superview. NSView doesn't implement the NSCopying protocol so you can't copy a view by calling copy. Search SO for 'copy NSView'.
It looks like the issue is that you are creating a new view that is equal to the view you want to copy. However, NSViews are reference types, not value types. So editing one view will edit the other.
NSView luckily implements the NSCoding protocol, so you can duplicate the view like this:
let archivedView = NSKeyedArchiver.archivedDataWithRootObject(contentView1)
let hc = NSKeyedUnarchiver.unarchiveObjectWithData(archivedView)
And then do the rest:
hc?.setFrameOrigin(NSPoint(x: 0, y: (positionY*50)))
positionY += 1
contentView2?.addSubview(hc!)
Related
Implement the proposed solution to do drag and drop in this thread:
SwiftUI | Using onDrag and onDrop to reorder Items within one single LazyGrid?
The problem I have is that I want to play a vibration when the drag starts and I don't get it.
I did the following:
.onDrag(){
let impact = UIImpactFeedbackGenerator(style: .light)
impact.impactOccurred()
self.dragging = module
return NSItemProvider(object: String(module.id) as NSString)
}
But if I drop the item on itself (to cancel or by accident). The next time I try to drag it, the onDrag() method is not executed.
How could I fix it?
On the iPad, I'm trying to build a simple app that continuously tracks the pointer's coordinates as it moves across the screen (via external mouse/trackpad). Basically something like this JavaScript example # 4:13 except in a SwiftUI view on the iPad.
MacOS has NSEvent.mouseLocation, but it doesn't seem like there's an iPadOS counterpart. Every resource I've come across online necessarily associates coordinates with a gesture (rotation, pinch, drag, click, etc.) with no way to respond to only cursor movement. This leads me to believe that the solution for pure pointer movement is likely independent of the Gesture protocol.
I was able to get halfway there after modifying code from this SO post. My code below displays updated mouse coordinates so long as the pointer is dragging (i.e., while at least one button is pressed).
import SwiftUI
struct ContentView: View {
#State private var pt = CGPoint()
#State private var txt = "init"
var body: some View {
let myGesture = DragGesture(
minimumDistance: 0,
coordinateSpace: .local
)
.onChanged {
self.pt = $0.location
self.txt = "x: \(self.pt.x), y: \(self.pt.y)"
}
// Spacers needed to make the VStack occupy the whole screen
return VStack {
Spacer()
Text(self.txt)
Spacer()
}
.frame(width: 1000, height: 1000)
.border(Color.green)
.contentShape(Rectangle()) // Make the entire VStack tappabable, otherwise, only the area with text generates a gesture
.gesture(myGesture) // Add the gesture to the Vstack
}
}
Now, how do I achieve this effect without needing to drag? Could there be some way to do this with the help of objective-C if a pure Swift solution isn't possible?
Thanks
Edit:
The WWDC video covers this very topic:
Handle trackpad and mouse input
Add SupportsIndirectInputEvents to your Info.plist
From the video:
It is required in order to get the new touch type indirect pointer and
EventType.transform.
Existing projects do not have this key set and will need to add it. Starting with iOS 14 and macOS Big Sur SDKs, new UIKit and SwiftUI
projects will have this value set to "true."
In addition you will use UIPointerInteraction. This tutorial shows you step by step including custom cursors:
https://pspdfkit.com/blog/2020/supporting-pointer-interactions/
I have a view with alot of logic code that would be better of in a viewmodel. The problem is that the code has references to the view which means that the viewmodel would need an object from the view to work properly. If I understand the concept of a viewmodel correctly, having a two-way dependency defeats the whole purpose of a viewmodel
As you can see from the code below it contains the line addChild(x) as well as .removeFromParent. So if moved to a viewmodel the code would have to be something like: mainView.addChild(x). It works sure enough, but my question is if it's appropriate. If not, should I just keep the code in the view, even though it takes up alot of space and has alot of logic in it?
These functions are used in the native touchDown() function:
func characterIsSelected(i: Int, j: Int) {
//Check Accessibility of Tile
if !gameBoardArray[i][j].isAccessibilityElement {
hasBeenClickedOnce = true
characterIsSelected = true
characterVM.getGameBoardPosition().fillColor = SKColor.brown
print("NOT ACCESSIBLE")
return
}else {
print("Moving")
viewModel.placeCharacter(row: i, col: j)
buttonIsAvailable = false
//SEE HERE
guiButton.removeFromParent()
enemyProximityCheck(i: i, j: j)
}
}
func enemyProximityCheck(i: Int, j: Int){
print("ENEMY PROXIMITY")
//SCAN VICINITY OF CHARACTER. IF ENEMY IS ON TILE, GOTO COMBAT
for ii in -1...1 {
for jj in -1...1 {
//gameBoardArray[i + (ii)][j + (jj)].fillColor = SKColor.blue
if(i + (ii) < 0 || j + (jj) < 0 ) {
print("LOW")
}
else if(i + (ii) > gameBoardArray.count - 1 || j + (jj) > gameBoardArray.count - 1) {
print("HIGH")
}
else {
if (gameBoardArray[i + (ii)][j + (jj)].contains(enemyVM.getCurrentEnemyPosition() ) ) {
print("AAARGH AN ENEMY!")
//SEE HERE
addChild(guiButton)
buttonIsAvailable = true
//enemyHasBeenSpotted = true
}
}
}
}//End for
}
Generally if you’re using a “view model”, the view model would not generally be manipulating the view directly. It would simply communicate the “encountered enemy” (or whatever) event to the “view” (which many of us would consider to be both the UIView hierarchy and the view controller, too), which would then be responsible for manipulating the view(s) accordingly.
Unfortunately, it’s hard to say precisely how the view model would do this in your case, on the basis of what you’ve outlined. Some use the term “view model” loosely, so there are a couple of possible interpretations:
One approach is to have the view observe (through some “binding” mechanism) some property in the view model, and when the view model updates that state, the view would then add/configure the appropriate views/subviews accordingly. (This is binding/observing is arguably an essential aspect of any “view model” based approach.)
Other approaches include protocol-delegate or closure patterns for the view model to communicate that UI updates to the view. (This is arguably more of a MVP sort of approach, though I’ve seen teams use the term “view model” in this context, too.)
But the consistent pattern is that the view model should generally not be reaching in and manipulating the view hierarchy itself. It should simply communicate (one way or the other) the state change, and the “view” would take it over from there.
FYI, I think Medium’s iOS Architecture Patterns is illuminating when talking about these various patterns in the context of iOS apps.
If you want to be a purist about MVC design, then game logic would go in the controller, not the view or the model. Their roles should be:
model: The current state of the game; e.g. which characters are in which game board array slots. It knows nothing about the view or the controller.
view: The visual representation of the game board. It reads from the model, but doesn't necessarily hold a reference to it.
controller: The game logic. It responds to user actions by updating the model and telling the view to redraw itself based on the model. It has strong references to the view and the model.
That said, if your game logic is fairly simple, then separating things out into a model, view, and controller may be overkill. Most of the time, I start with a view controller class file and a corresponding view controller in a storyboard. If it gets too complex, then I create a view and/or model. The view controller has an outlet to the view, the view has outlets to its subviews, and the view's controls are connected to IBActions in the view controller.
UPDATE: Solved! While the contentMode for pianoNoteDisplayed and piano_background were indeed the same, apparently this wasn't true for the added subviews. I simply added the line subview.contentMode = superview.contentMode to the function vdmzz suggested, and now everything looks right on all 4 screen sizes.
There are two image views: one called "piano_background" holds a background image (a piano keyboard) and the other will be used to display highlighted notes. The second is constrained to the first:
(the width constraint is probably unnecessary, because the leading and trailing constraints are already set, right?)
To display multiple highlighted keys, I am programmatically adding subviews to the piano_note view and activating the NSLayoutConstraints to get it into place (otherwise it shows up way out of position) like so:
pianoNoteDisplayed.image = nil
if !notesAlreadyAttempted.contains(currentUserAnswer) {
let wrongNoteImageName = "large_\(currentUserAnswer)_wrong"
let wrongNoteImage = UIImage(named: wrongNoteImageName)
let wrongNoteImageView = UIImageView(image: wrongNoteImage!)
wrongNoteImageView.translatesAutoresizingMaskIntoConstraints = false
pianoNoteDisplayed.addSubview(wrongNoteImageView)
NSLayoutConstraint.activate([
wrongNoteImageView.widthAnchor.constraint(equalToConstant: pianoNoteDisplayed!.frame.width),
wrongNoteImageView.heightAnchor.constraint(equalToConstant: pianoNoteDisplayed!.frame.height)
])
}
notesAlreadyAttempted.append(currentUserAnswer)
}
The issue is that the subview is displayed slightly off, and I can't seem to figure out why:
(as you can see, the highlight looks slightly compressed vertically.. the top lands correctly, but the bottom doesn't reach far enough by about 5px)
I have tried centering and constraining the subview in multiple ways, using suggestions from about 5 different answers on stack, and a few other articles I found. The images I am using (the piano background and the overlaying note highlight subview) are identical sizes. I have tried adding more or fewer constraints in the interface builder, and I have tried adding subviews to the original piano_background view instead of the second pianoNoteDisplayed view - same result. Using the pianoNoteDisplayed view itself to display the highlighted note works fine by the way:
And these are displayed using the usual .image method:
pianoNoteDisplayed.image = UIImage(named: "large_\(currentCorrectAnswer)_right")
Any suggestions for how to troubleshoot the issue further?
First of all, as far as I understood pianoNoteDisplayed doesn't need to be an UIImageView.
Secondly, if you align piano_background and pianoNoteDisplayed by top, leading, trailing and bottom edges, one will be exactly on top of other. Or you could set them equal height, width and center positions.
The problem with your current set of constraints is that piano_background's Y position is determined by Safe Area and therefore might defer from pianoNoteDisplayed's Y position.
Try using this function:
func addSameSize(subview: UIView, onTopOf superview: UIView) {
superview.addSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = false
subview.centerXAnchor.constraint(equalTo: superview.centerXAnchor).isActive = true
subview.centerYAnchor.constraint(equalTo: superview.centerYAnchor).isActive = true
subview.widthAnchor.constraint(equalTo: superview.widthAnchor).isActive = true
subview.heightAnchor.constraint(equalTo: superview.heightAnchor).isActive = true
subview.contentMode = superview.contentMode
}
E.g.
addSameSize(subview: wrongNoteImageView, onTopOf: pianoNoteDisplayed)
It will add your image views exactly aligned on top of pianoNoteDisplayed view
I know I'm missing something, because this has to be something easy to achieve.
My problem is that I have in my "loading screen" (the one that appears right after the splash) an UIImageView with two different images for 3.5" and 4" size screen. In a certain place of that images, I put one UIActivityIndicator, to tell the user that the app is loading something in the background. That place is not the same for both images, because one of them is obviously higher that the other, so I want to set an autolayout constraint that allows me to put that activity indicator at different heights, depends on if the app is running in an iPhone 5 or not.
Without Autolayout, I'd set the frame.origin.y of the view to 300 (for example), and then in the viewDidLoad method of the ViewController, I'd ask if the app is running in an iPhone 5, so I'd change the value to, for example, 350. I have no idea how to do this using Autolayout and I think it has to be pretty simple.
You can create an NSLayoutConstraint outlet on your view controller and connect the outlet to the activity indicator's Y constraint in your xib or storyboard. Then, add an updateViewContraints method to your view controller and update the constraint's constant according to the screen size.
Here's an example of updateViewConstraints:
- (void)updateViewConstraints {
[super updateViewConstraints];
self.activityIndicatorYConstraint.constant =
[UIScreen mainScreen].bounds.size.height > 480.0f ? 200 : 100;
}
Of course you will want to put in your appropriate values instead of 200 and 100. You might want to define some named constants. Also, don't forget to call [super updateViewConstraints].
The problem of #Rob answer's is you should do a lot of code for each constraint.
So to resolve that, just add ConstraintLayout class to your code and modify constraint constant value for the device that you want in the IB :
//
// LayoutConstraint.swift
// MyConstraintLayout
//
// Created by Hamza Ghazouani on 19/05/2016.
// Copyright © 2016 Hamza Ghazouani. All rights reserved.
//
import UIKit
#IBDesignable
class LayoutConstraint: NSLayoutConstraint {
#IBInspectable
var 📱3¨5_insh: CGFloat = 0 {
didSet {
if UIScreen.main.bounds.maxY == 480 {
constant = 📱3¨5_insh
}
}
}
#IBInspectable
var 📱4¨0_insh: CGFloat = 0 {
didSet {
if UIScreen.main.bounds.maxY == 568 {
constant = 📱4¨0_insh
}
}
}
#IBInspectable
var 📱4¨7_insh: CGFloat = 0 {
didSet {
if UIScreen.main.bounds.maxY == 667 {
constant = 📱4¨7_insh
}
}
}
#IBInspectable
var 📱5¨5_insh: CGFloat = 0 {
didSet {
if UIScreen.main.bounds.maxY == 736 {
constant = 📱5¨5_insh
}
}
}
}
Don't forgot to inherit your class constraint from ConstraintLayout
I will add the objective-c version soon
The basic tool in Auto Layout to manage UI objects' position is the Constraints. A constraint describes a geometric relationship between two views. For example, you might have a constraint that says:
“The right edge of progress bar is connected to the left edge of a lable 40 points of empty space between them.”
This means using AutoLayout you can't do conditional position setting based on UIDevice's mode, rather you can create a view layout which modifies itself if eg. the app runs on 3.5' full screen (IPhone4) or 4' full screen (IPhone5) based on the constraints.
So options for your problem using Constraints:
1) find a view on your layout which can be used to create a constraint to position the progressbar relatively. (select the view and the progressbar using CMD button, then use Editor/Pin/Vertical Spacing menu item to create a vertical constraint between the 2 objects)
2) create an absolute constraint to stick the progressbar's position to screen edge (keeping space) or centrally
I found helpful this tutorial about AutoLayout which might be beneficial for you also:
http://www.raywenderlich.com/20881/beginning-auto-layout-part-1-of-2
Pls note: autolayout only works from IOS 6.
The new way, Without writing a single line!
No need to write device based conditions like these :-
if device == iPhoneSE {
constant = 44
} else if device == iPhone6 {
constant = 52
}
I created a library Layout Helper so now you can update constraint for each device without writing a single line of code.
Step 1
Assign the NSLayoutHelper to your constraint
Step 2
Update the constraint for the device you want
Step 3
Run the app and see the MAGIC
I generally always try to stay in Interface Builder for setting up constraints. Diving in code to have more control is usually useful if you have completely different layouts on iPhone 4 and 6 for example.
As mentioned before, you can't have conditionals in Interface Builder, that's when linking a constraint to your view controller really comes handy.
Here's a short explanation on 3 approaches to solve Auto Layout issues for different screen sizes: http://candycode.io/how-to-set-up-different-auto-layout-constraints-for-different-screen-sizes/