When programmed as an input, GPIO has extremely high input resistance (open circuit).
I understand how when the switch is open, the current must flow from VCC to the input pin through the resistor and I read a high voltage.
Because of the very high input resistance, practically there is no current flowing into the GPIO pin. In this situation, the resistor transfers voltage without current - there is no current through it so there is no voltage drop across it and the input voltage is equal to Vcc.
By the way, this phenomenon is not understood by many people because it contradicts human intuition - there is a resistor but it does not resist anything (the resistor is not a resistor).
The situation is the same if there is a built-in pull-up resistor inside the controller - the two resistors would be in parallel.
When the switch is closed, the current flows from VCC to GND and the input pin reads low.
Zero input voltage. Exactly! The current flows through a "piece of wire" so there is no voltage drop across it and the input voltage is zero.
Why doesn't the current also flow to the input pin (as well as GND) in this case?
Zero input current. Like a human being:-) it always chooses the "easiest" path (lowest resistance). So, in this particular case, whatever is inside the input the current would not flow in... especially since it has a high resistance.
But why does the current flow out of the input pin to GND, does the input pin have nonzero voltage?
When current exits the input. The case is more interesting if there is a built-in pull-up resistor inside the controller. Then a current exits the input and is added to the current set by the external pull-up resistor (the two pull-up resistors - external and internal, are in parallel). But the result is the same - there is no voltage drop because the switch resistance is zero and the input voltage is zero.
A similar situation occurred when the input was TTL... but that was a thing of the past...
More points of view
"Fighting" pull resistors. You can imagine the switch as a pull-down variable resistor with two resistance values - zero and infinity. Then two elements - a constant and variable resistor, pull the common point in opposite directions - the constant resistor pulls it up and the variable resistor (switch) pulls it down.
"Voltage-divider". Also, you can consider the circuit of the pull-up resistor and switch in series as a voltage divider with transfer ratio K = R2/(R1 + R2). When the switch is open, K = 1; when it is closed, K = 0.
This case is discussed in detail by @TonyM in their answer. I will consider the problems only at the conceptual level necessary for understanding.
Generally speaking, there is some "pull-up" part of the input circuit connected internally between the input and Vcc, and some "pull-down" part connected between the input and ground. Both parts have extremely high resistance. So, figuratively speaking, something like a high-resistance "voltage divider" is formed and we can use it as an "electrical analogy" to illustrate the problem.
simulate this circuit – Schematic created using CircuitLab
... does the input pin have nonzero voltage?
There is such a voltage... and that is confusing - it is an input but produces a voltage? For example, the midpoint of the voltage divider R1-R2 is its output, but here it represents the input of the Arduino.
We know that the midpoint voltage Vmid is between zero (ground) and Vcc. We also know that current flows from higher to lower voltage.
High input voltage. So when we apply a voltage Vcc > Vmid through a resistor to this point (just connecting the resistor to Vcc), a very small current begins to flow into it.
simulate this circuit
Low input voltage. When we apply a zero voltage through a resistor to this point (just connecting the resistor to ground), a very small current starts coming out of it.
simulate this circuit